playmp_controller.cpp

Go to the documentation of this file.
00001 /* $Id: playmp_controller.cpp 25991 2008-04-21 21:55:02Z alink $ */
00002 /*
00003    Copyright (C) 2006 - 2008 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 version 2
00009    or at your option any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 #include "playmp_controller.hpp"
00017 
00018 #include "dialogs.hpp"
00019 #include "game_errors.hpp"
00020 #include "gettext.hpp"
00021 #include "log.hpp"
00022 #include "playturn.hpp"
00023 #include "sound.hpp"
00024 #include "upload_log.hpp"
00025 
00026 #include <cassert>
00027 
00028 #define LOG_NG LOG_STREAM(info, engine)
00029 
00030 unsigned int playmp_controller::replay_last_turn_ = 0;
00031 
00032 playmp_controller::playmp_controller(const config& level,
00033     game_state& state_of_game, const int ticks,
00034     const int num_turns, const config& game_config, CVideo& video,
00035     bool skip_replay, bool is_host)
00036     : playsingle_controller(level, state_of_game, ticks, num_turns,
00037         game_config, video, skip_replay)
00038 {
00039     beep_warning_time_ = 0;
00040     turn_data_ = NULL;
00041     is_host_ = is_host;
00042     // We stop quick replay if play isn't yet past turn 1
00043     if ( replay_last_turn_ <= 1)
00044     {
00045         skip_replay_ = false;
00046     }
00047 }
00048 
00049 playmp_controller::~playmp_controller() {
00050     //halt and cancel the countdown timer
00051     if(beep_warning_time_ < 0) {
00052         sound::stop_bell();
00053     }
00054 }
00055 
00056 void playmp_controller::set_replay_last_turn(unsigned int turn){
00057      replay_last_turn_ = turn;
00058 }
00059 
00060 void playmp_controller::clear_labels(){
00061     menu_handler_.clear_labels();
00062 }
00063 
00064 void playmp_controller::speak(){
00065     menu_handler_.speak();
00066 }
00067 
00068 void playmp_controller::whisper(){
00069     menu_handler_.whisper();
00070 }
00071 
00072 void playmp_controller::shout(){
00073     menu_handler_.shout();
00074 }
00075 
00076 void playmp_controller::play_side(const unsigned int team_index, bool save){
00077     do {
00078         player_type_changed_ = false;
00079         end_turn_ = false;
00080 
00081         statistics::reset_turn_stats(player_number_);
00082 
00083         // we can't call playsingle_controller::play_side because
00084         // we need to catch exception here
00085         if(current_team().is_human()) {
00086             LOG_NG << "is human...\n";
00087 
00088             // reset default state
00089             beep_warning_time_ = 0;
00090 
00091             try{
00092                 before_human_turn(save);
00093                 play_human_turn();
00094                 after_human_turn();
00095             } catch(end_turn_exception& end_turn) {
00096                 if (end_turn.redo == team_index) {
00097                     player_type_changed_ = true;
00098                     // if new controller is not human,
00099                     // reset gui to prev human one
00100                     if (!teams_[team_index-1].is_human()) {
00101                         int t = find_human_team_before(team_index);
00102 
00103                         if (t <= 0)
00104                             t = gui_->get_playing_team() + 1;
00105 
00106                         gui_->set_team(t-1);
00107                         gui_->recalculate_minimap();
00108                         gui_->invalidate_all();
00109                         gui_->draw(true,true);
00110                     }
00111                 }
00112             }
00113             LOG_NG << "human finished turn...\n";
00114         } else if(current_team().is_ai()) {
00115             play_ai_turn();
00116         } else if(current_team().is_network()) {
00117             play_network_turn();
00118         }
00119     } while (player_type_changed_);
00120     //keep looping if the type of a team (human/ai/networked) has changed mid-turn
00121 }
00122 
00123 void playmp_controller::before_human_turn(bool save){
00124     playsingle_controller::before_human_turn(save);
00125 
00126     turn_data_ = new turn_info(gamestate_,status_,
00127         *gui_,map_,teams_,player_number_,units_,replay_sender_, undo_stack_);
00128     turn_data_->replay_error().attach_handler(this);
00129     turn_data_->host_transfer().attach_handler(this);
00130 }
00131 
00132 bool playmp_controller::counting_down() {
00133     return beep_warning_time_ > 0;
00134 }
00135 
00136 namespace {
00137     const int WARNTIME = 20000; //start beeping when 20 seconds are left (20,000ms)
00138     unsigned timer_refresh = 0;
00139     const unsigned timer_refresh_rate = 50; // prevents calling SDL_GetTicks() too frequently
00140 }
00141 
00142 //make sure we think about countdown even while dialogs are open
00143 void playmp_controller::process(events::pump_info &info) {
00144     if(playmp_controller::counting_down()) {
00145         if(info.ticks(&timer_refresh, timer_refresh_rate)) {
00146             playmp_controller::think_about_countdown(info.ticks());
00147         }
00148     }
00149 }
00150 
00151 //check if it is time to start playing the timer warning
00152 void playmp_controller::think_about_countdown(int ticks) {
00153     if(ticks >= beep_warning_time_) {
00154         const bool bell_on = preferences::turn_bell();
00155         if(bell_on || preferences::sound_on() || preferences::UI_sound_on()) {
00156             const int loop_ticks = WARNTIME - (ticks - beep_warning_time_);
00157             const int fadein_ticks = (loop_ticks > WARNTIME / 2) ? loop_ticks - WARNTIME / 2 : 0;
00158             sound::play_timer(game_config::sounds::timer_bell, loop_ticks, fadein_ticks);
00159             beep_warning_time_ = -1;
00160         }
00161     }
00162 }
00163 
00164 void playmp_controller::play_human_turn(){
00165     int cur_ticks = SDL_GetTicks();
00166 
00167     if ((!linger_) || (is_host_))
00168         gui_->enable_menu("endturn", true);
00169     while(!end_turn_) {
00170 
00171         try {
00172             config cfg;
00173             const network::connection res = network::receive_data(cfg);
00174             std::deque<config> backlog;
00175 
00176             if(res != network::null_connection) {
00177                 try{
00178                     if (turn_data_->process_network_data(cfg,res,backlog,skip_replay_) == turn_info::PROCESS_RESTART_TURN)
00179                     {
00180                         throw end_turn_exception(gui_->get_playing_team() + 1);
00181                     }
00182                 }
00183                 catch (replay::error& e){
00184                     process_oos(e.message);
00185                     throw e;
00186                 }
00187             }
00188 
00189             play_slice();
00190         } catch(end_level_exception& e) {
00191             turn_data_->send_data();
00192             throw e;
00193         }
00194 
00195         if (!linger_ && (current_team().countdown_time() > 0) && (level_["mp_countdown"] == "yes")) {
00196             SDL_Delay(1);
00197             const int ticks = SDL_GetTicks();
00198             int new_time = current_team().countdown_time()-maximum<int>(1,(ticks - cur_ticks));
00199             if (new_time > 0 ){
00200                 current_team().set_countdown_time(new_time);
00201                 cur_ticks = ticks;
00202                 if(current_team().is_human() && !beep_warning_time_) {
00203                     beep_warning_time_ = new_time - WARNTIME + ticks;
00204                 }
00205                 if(counting_down()) {
00206                     think_about_countdown(ticks);
00207                 }
00208             } else {
00209                 // Clock time ended
00210                 // If no turn bonus or action bonus -> defeat
00211                 const int action_increment = lexical_cast_default<int>(level_["mp_countdown_action_bonus"],0);
00212                 if ( lexical_cast_default<int>(level_["mp_countdown_turn_bonus"],0) == 0
00213                     && (action_increment == 0 || current_team().action_bonus_count() == 0)) {
00214                     // Not possible to end level in MP with throw end_level_exception(DEFEAT);
00215                     // because remote players only notice network disconnection
00216                     // Current solution end remaining turns automatically
00217                     current_team().set_countdown_time(10);
00218                 } else {
00219                     const int maxtime = lexical_cast_default<int>(level_["mp_countdown_reservoir_time"],0);
00220                     int secs = lexical_cast_default<int>(level_["mp_countdown_turn_bonus"],0);
00221                     secs += action_increment  * current_team().action_bonus_count();
00222                     current_team().set_action_bonus_count(0);
00223                     secs = (secs > maxtime) ? maxtime : secs;
00224                     current_team().set_countdown_time(1000 * secs);
00225                 }
00226                 recorder.add_countdown_update(current_team().countdown_time(),player_number_);
00227                 recorder.end_turn();
00228                 turn_data_->send_data();
00229 
00230                 throw end_turn_exception();
00231             }
00232         }
00233 
00234         gui_->draw();
00235 
00236         turn_data_->send_data();
00237     }
00238     menu_handler_.clear_undo_stack(player_number_);
00239 }
00240 
00241 void playmp_controller::set_end_scenario_button()
00242 {
00243     // Modify the end-turn button
00244     if (! is_host_) {
00245         gui::button* btn_end = gui_->find_button("button-endturn");
00246         btn_end->enable(false);
00247     }
00248     gui_->get_theme().refresh_title("button-endturn", _("End scenario"));
00249     gui_->invalidate_theme();
00250     gui_->redraw_everything();
00251 }
00252 
00253 void playmp_controller::reset_end_scenario_button()
00254 {
00255     // revert the end-turn button text to its normal label
00256     gui_->get_theme().refresh_title2("button-endturn", "title");
00257     gui_->invalidate_theme();
00258     gui_->redraw_everything();
00259     gui_->set_game_mode(game_display::RUNNING);
00260 }
00261 
00262 void playmp_controller::linger(upload_log& log)
00263 {
00264     LOG_NG << "beginning end-of-scenario linger\n";
00265     browse_ = true;
00266     linger_ = true;
00267     // If we need to set the status depending on the completion state
00268     // we're needed here.
00269     gui_->set_game_mode(game_display::LINGER_MP);
00270 
00271     // this is actually for after linger mode is over -- we don't want to
00272     // stay stuck in linger state when the *next* scenario is over.
00273     gamestate_.completion = "running";
00274     // End all unit moves
00275     for (unit_map::iterator u = units_.begin(); u != units_.end(); u++) {
00276         u->second.set_user_end_turn(true);
00277     }
00278     //current_team().set_countdown_time(0);
00279     //halt and cancel the countdown timer
00280     if(beep_warning_time_ < 0) {
00281         sound::stop_bell();
00282     }
00283     beep_warning_time_=-1;
00284 
00285     set_end_scenario_button();
00286 
00287     // switch to observer viewpoint
00288     gui_->set_team(0,true);
00289     gui_->recalculate_minimap();
00290     gui_->invalidate_all();
00291     gui_->draw(true,true);
00292 
00293     bool quit;
00294     do {
00295         quit = true;
00296         try {
00297             // reimplement parts of play_side()
00298             player_number_ = first_player_;
00299             turn_data_ = new turn_info(gamestate_, status_,
00300                                        *gui_,map_, teams_, player_number_,
00301                                        units_, replay_sender_, undo_stack_);
00302             turn_data_->replay_error().attach_handler(this);
00303             turn_data_->host_transfer().attach_handler(this);
00304 
00305             play_human_turn();
00306             after_human_turn();
00307             LOG_NG << "finished human turn" << std::endl;
00308         } catch (game::load_game_exception&) {
00309             LOG_NG << "caught load-game-exception" << std::endl;
00310             // this should not happen, the option to load a game is disabled
00311             log.quit(status_.turn());
00312             throw;
00313         } catch (end_level_exception&) {
00314             // thrown if the host ends the scenario and let us advance
00315             // to the next level
00316             LOG_NG << "caught end-level-exception" << std::endl;
00317             reset_end_scenario_button();
00318             throw;
00319         } catch (end_turn_exception&) {
00320             // thrown if the host leaves the game (sends [leave_game]), we need
00321             // to stay in this loop to stay in linger mode, otherwise the game
00322             // gets aborted
00323             LOG_NG << "caught end-turn-exception" << std::endl;
00324             quit = false;
00325         } catch (network::error&) {
00326             LOG_NG << "caught network-error-exception" << std::endl;
00327             quit = false;
00328         }
00329     } while (!quit);
00330 
00331     reset_end_scenario_button();
00332 
00333     LOG_NG << "ending end-of-scenario linger\n";
00334 }
00335 
00336 //! Wait for the host to upload the next scenario.
00337 void playmp_controller::wait_for_upload()
00338 {
00339     // If the host is here we'll never leave since we wait for the host to
00340     // upload the next scenario.
00341     assert(!is_host_);
00342 
00343     const bool set_turn_data = (turn_data_ == 0);
00344     if(set_turn_data) {
00345         turn_data_ = new turn_info(gamestate_,status_,
00346                         *gui_,map_,teams_,player_number_,units_,replay_sender_, undo_stack_);
00347         turn_data_->replay_error().attach_handler(this);
00348         turn_data_->host_transfer().attach_handler(this);
00349     }
00350 
00351     while(true) {
00352         try {
00353             config cfg;
00354             const network::connection res = dialogs::network_receive_dialog(
00355                 *gui_, _("Waiting for next scenario..."), cfg);
00356 
00357             std::deque<config> backlog;
00358             if(res != network::null_connection) {
00359                 try{
00360                     if(turn_data_->process_network_data(cfg,res,backlog,skip_replay_)
00361                             == turn_info::PROCESS_END_LINGER) {
00362                         break;
00363                     }
00364                 }
00365                 catch (replay::error& e){
00366                     process_oos(e.message);
00367                     throw e;
00368                 }
00369             }
00370 
00371         } catch(end_level_exception& e) {
00372             turn_data_->send_data();
00373             throw e;
00374         }
00375     }
00376 
00377     if(set_turn_data) {
00378         delete turn_data_;
00379         turn_data_ = 0;
00380     }
00381 }
00382 
00383 void playmp_controller::after_human_turn(){
00384     if ( level_["mp_countdown"] == "yes" ){
00385         const int action_increment = lexical_cast_default<int>(level_["mp_countdown_action_bonus"],0);
00386         const int maxtime = lexical_cast_default<int>(level_["mp_countdown_reservoir_time"],0);
00387         int secs = (current_team().countdown_time() / 1000) + lexical_cast_default<int>(level_["mp_countdown_turn_bonus"],0);
00388         secs += action_increment  * current_team().action_bonus_count();
00389         current_team().set_action_bonus_count(0);
00390         secs = (secs > maxtime) ? maxtime : secs;
00391         current_team().set_countdown_time(1000 * secs);
00392         recorder.add_countdown_update(current_team().countdown_time(),player_number_);
00393     }
00394     end_turn_record();
00395 
00396     //send one more time to make sure network is up-to-date.
00397     turn_data_->send_data();
00398     if (turn_data_ != NULL){
00399         turn_data_->replay_error().detach_handler(this);
00400         turn_data_->host_transfer().detach_handler(this);
00401         delete turn_data_;
00402         turn_data_ = NULL;
00403     }
00404 
00405     playsingle_controller::after_human_turn();
00406 }
00407 
00408 void playmp_controller::finish_side_turn(){
00409     play_controller::finish_side_turn();
00410 
00411     //just in case due to an exception turn_data_ has not been deleted in after_human_turn
00412     delete turn_data_;
00413     turn_data_ = NULL;
00414 
00415     //halt and cancel the countdown timer
00416     if(beep_warning_time_ < 0) {
00417         sound::stop_bell();
00418     }
00419 }
00420 
00421 void playmp_controller::play_network_turn(){
00422     LOG_NG << "is networked...\n";
00423 
00424     browse_ = true;
00425     gui_->enable_menu("endturn", false);
00426     turn_info turn_data(gamestate_,status_,*gui_,
00427                 map_,teams_,player_number_,units_, replay_sender_, undo_stack_);
00428     turn_data.replay_error().attach_handler(this);
00429     turn_data.host_transfer().attach_handler(this);
00430 
00431     for(;;) {
00432 
00433         bool have_data = false;
00434         config cfg;
00435 
00436         network::connection from = network::null_connection;
00437 
00438         if(data_backlog_.empty() == false) {
00439             have_data = true;
00440             cfg = data_backlog_.front();
00441             data_backlog_.pop_front();
00442         } else {
00443             from = network::receive_data(cfg);
00444             have_data = from != network::null_connection;
00445         }
00446 
00447         if(have_data) {
00448             if (skip_replay_ && replay_last_turn_ <= status_.turn()){
00449                     skip_replay_ = false;
00450             }
00451             try{
00452                 const turn_info::PROCESS_DATA_RESULT result = turn_data.process_network_data(cfg,from,data_backlog_,skip_replay_);
00453                 if(result == turn_info::PROCESS_RESTART_TURN) {
00454                     player_type_changed_ = true;
00455                     return;
00456                 } else if(result == turn_info::PROCESS_END_TURN) {
00457                     break;
00458                 }
00459             }
00460             catch (replay::error e){
00461                 process_oos(e.message);
00462                 throw e;
00463             }
00464 
00465         }
00466 
00467         play_slice();
00468         turn_data.send_data();
00469         gui_->draw();
00470     }
00471 
00472     turn_data.replay_error().detach_handler(this);
00473     turn_data.host_transfer().detach_handler(this);
00474     LOG_NG << "finished networked...\n";
00475     return;
00476 }
00477 
00478 void playmp_controller::process_oos(const std::string& err_msg){
00479     std::stringstream temp_buf;
00480     std::vector<std::string> err_lines = utils::split(err_msg,'\n');
00481     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?");
00482     if(!err_msg.empty()) {
00483         temp_buf << " \n \n"; //and now the "Details:"
00484         for(std::vector<std::string>::iterator i=err_lines.begin(); i!=err_lines.end(); i++)
00485         {
00486             temp_buf << "`#" << *i << '\n';
00487         }
00488         temp_buf << " \n";
00489     }
00490     menu_handler_.save_game(temp_buf.str(),gui::YES_NO, true);
00491 }
00492 
00493 void playmp_controller::handle_generic_event(const std::string& name){
00494     turn_info turn_data(gamestate_,status_,*gui_,
00495                         map_,teams_,player_number_,units_, replay_sender_, undo_stack_);
00496 
00497     if (name == "ai_user_interact"){
00498         playsingle_controller::handle_generic_event(name);
00499         turn_data.send_data();
00500     }
00501     else if ((name == "ai_unit_recruited") || (name == "ai_unit_moved")
00502         || (name == "ai_enemy_attacked")){
00503         turn_data.sync_network();
00504     }
00505     else if (name == "network_replay_error"){
00506         process_oos(replay::last_replay_error);
00507     }
00508     else if (name == "host_transfer"){
00509         is_host_ = true;
00510         if (linger_){
00511             gui::button* btn_end = gui_->find_button("button-endturn");
00512             btn_end->enable(true);
00513             gui_->invalidate_theme();
00514         }
00515     }
00516 }
00517 
00518 bool playmp_controller::can_execute_command(hotkey::HOTKEY_COMMAND command, int index) const
00519 {
00520     bool res = true;
00521     switch (command){
00522         case hotkey::HOTKEY_CLEAR_LABELS:
00523             res = !is_observer();
00524         case hotkey::HOTKEY_SPEAK:
00525         case hotkey::HOTKEY_SPEAK_ALLY:
00526         case hotkey::HOTKEY_SPEAK_ALL:
00527             res = res && network::nconnections() > 0;
00528             break;
00529         default:
00530             return playsingle_controller::can_execute_command(command, index);
00531     }
00532     return res;
00533 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs