From 9d096bef24a5a8cb30f1d556dadcc8e5054d1bff Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Thu, 26 Feb 2015 22:51:18 +0100 Subject: [PATCH 01/10] remove strange playmp_controller_.stop_network() call can_execute_command should just check whether a command can be executed. Idk why we called playmp_controller_.stop_network(); there. --- src/hotkey_handler_mp.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/hotkey_handler_mp.cpp b/src/hotkey_handler_mp.cpp index 61ee68e2fb41..7b310efe11d2 100644 --- a/src/hotkey_handler_mp.cpp +++ b/src/hotkey_handler_mp.cpp @@ -72,11 +72,6 @@ bool playmp_controller::hotkey_handler::can_execute_command(const hotkey::hotkey case hotkey::HOTKEY_STOP_NETWORK: res = is_observer(); break; - case hotkey::HOTKEY_REPLAY_STOP: - if (is_observer()){ - playmp_controller_.stop_network(); - } - break; default: return playsingle_controller::hotkey_handler::can_execute_command(cmd, index); } From 8b83504b5bcba8d3895444c71e84b443a6bd95c2 Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Fri, 27 Feb 2015 00:18:33 +0100 Subject: [PATCH 02/10] remove field replay::skip_ We have the information already in play_controller_skip_replay_ --- src/actions/heal.cpp | 4 ++-- src/game_events/action_wml.cpp | 2 +- src/play_controller.cpp | 7 +++---- src/playmp_controller.cpp | 15 +++++---------- src/playsingle_controller.cpp | 4 ++-- src/playturn.cpp | 17 ++++++----------- src/playturn.hpp | 8 +++----- src/replay.cpp | 21 ++++----------------- src/replay.hpp | 5 ----- src/replay_controller.cpp | 8 +++----- 10 files changed, 29 insertions(+), 62 deletions(-) diff --git a/src/actions/heal.cpp b/src/actions/heal.cpp index a90ac892ffc6..b4ee998c2faf 100644 --- a/src/actions/heal.cpp +++ b/src/actions/heal.cpp @@ -24,7 +24,7 @@ #include "../gettext.hpp" #include "../log.hpp" #include "../map.hpp" -#include "../replay.hpp" +#include "../play_controller.hpp" #include "../resources.hpp" #include "../team.hpp" #include "../unit.hpp" @@ -352,7 +352,7 @@ void calculate_healing(int side, bool update_display) const team & viewing_team = (*resources::teams)[resources::screen->viewing_team()]; - if (!recorder.is_skipping() && update_display && + if (!resources::controller->is_skipping_replay() && update_display && patient.is_visible_to_team(viewing_team, resources::gameboard->map(), false) ) { unit_list.push_front(heal_unit(patient, healers, healing, curing == POISON_CURE)); diff --git a/src/game_events/action_wml.cpp b/src/game_events/action_wml.cpp index bdee1065b909..b73483f0d5e5 100644 --- a/src/game_events/action_wml.cpp +++ b/src/game_events/action_wml.cpp @@ -635,7 +635,7 @@ WML_HANDLER_FUNCTION(message, event_info, cfg) } has_input = !options.empty() || has_text_input; - if (!has_input && get_replay_source().is_skipping()) { + if (!has_input && resources::controller->is_skipping_replay()) { // No input to get and the user is not interested either. return; } diff --git a/src/play_controller.cpp b/src/play_controller.cpp index 954b62f53400..0ef6bde3c846 100644 --- a/src/play_controller.cpp +++ b/src/play_controller.cpp @@ -195,7 +195,6 @@ void play_controller::init(CVideo& video){ } loadscreen::start_stage("load level"); - recorder.set_skip(false); LOG_NG << "initializing game_state..." << (SDL_GetTicks() - ticks_) << std::endl; gamestate_.init(ticks_, *this); @@ -424,7 +423,7 @@ void play_controller::do_init_side() current_team().spend_gold(expense); } - calculate_healing(player_number_, !skip_replay_); + calculate_healing(player_number_, !is_skipping_replay()); } // Prepare the undo stack. @@ -448,11 +447,11 @@ void play_controller::init_side_end() if (player_number_ == first_player_) sound::play_sound(tod.sounds, sound::SOUND_SOURCES); - if (!recorder.is_skipping()){ + if (!is_skipping_replay()){ gui_->invalidate_all(); } - if (!recorder.is_skipping() && !skip_replay_ && current_team().get_scroll_to_leader()){ + if (!is_skipping_replay() && current_team().get_scroll_to_leader()){ gui_->scroll_to_leader(player_number_,game_display::ONSCREEN,false); } whiteboard_manager_->on_init_side(); diff --git a/src/playmp_controller.cpp b/src/playmp_controller.cpp index 84224a7588b6..cd8df332bc6e 100644 --- a/src/playmp_controller.cpp +++ b/src/playmp_controller.cpp @@ -140,7 +140,7 @@ possible_end_play_signal playmp_controller::play_human_turn() if(network_reader_.read(cfg)) { turn_info::PROCESS_DATA_RESULT res; - HANDLE_END_PLAY_SIGNAL( res = turn_data_.process_network_data(cfg, skip_replay_) ); + HANDLE_END_PLAY_SIGNAL( res = turn_data_.process_network_data(cfg) ); if (res == turn_info::PROCESS_RESTART_TURN) { // Clean undo stack if turn has to be restarted (losing control) @@ -212,7 +212,7 @@ possible_end_play_signal playmp_controller::play_idle_loop() config cfg; if(network_reader_.read(cfg)) { turn_info::PROCESS_DATA_RESULT res; - HANDLE_END_PLAY_SIGNAL( res = turn_data_.process_network_data(cfg, skip_replay_) ); + HANDLE_END_PLAY_SIGNAL( res = turn_data_.process_network_data(cfg) ); if (res == turn_info::PROCESS_RESTART_TURN) { @@ -332,7 +332,7 @@ void playmp_controller::wait_for_upload() *gui_, _("Waiting for next scenario..."), cfg); if(res != network::null_connection) { - if (turn_data_.process_network_data_from_reader(skip_replay_) == turn_info::PROCESS_END_LINGER) { + if (turn_data_.process_network_data_from_reader() == turn_info::PROCESS_END_LINGER) { break; } } @@ -388,12 +388,10 @@ possible_end_play_signal playmp_controller::play_network_turn(){ config cfg; if(network_reader_.read(cfg)) { if (replay_last_turn_ <= turn()){ - if (skip_replay_) { - skip_replay_ = false; - } + skip_replay_ = false; } turn_info::PROCESS_DATA_RESULT result; - HANDLE_END_PLAY_SIGNAL ( result = turn_data_.process_network_data(cfg, skip_replay_) ); + HANDLE_END_PLAY_SIGNAL ( result = turn_data_.process_network_data(cfg) ); if(player_type_changed_ == true) { //we received a player change/quit during waiting in get_user_choice/synced_context::pull_remote_user_input @@ -411,13 +409,10 @@ possible_end_play_signal playmp_controller::play_network_turn(){ */ else if(!recorder.at_end()) { - bool was_skipping = recorder.is_skipping(); - recorder.set_skip(skip_replay_); if(do_replay() == REPLAY_FOUND_END_TURN) { break; } - recorder.set_skip(was_skipping); } } diff --git a/src/playsingle_controller.cpp b/src/playsingle_controller.cpp index c81317633a44..da162fcd548d 100644 --- a/src/playsingle_controller.cpp +++ b/src/playsingle_controller.cpp @@ -117,7 +117,7 @@ void playsingle_controller::init_gui(){ } gui_->scroll_to_tile(gamestate_.board_.map().starting_position(1), game_display::WARP); - update_locker lock_display(gui_->video(),recorder.is_skipping()); + update_locker lock_display(gui_->video(), is_skipping_replay()); get_hotkey_command_executor()->set_button_state(*gui_); events::raise_draw_event(); gui_->draw(); @@ -239,7 +239,7 @@ boost::optional playsingle_controller::play_scenario_init(end_leve init_gui(); past_prestart = true; - LOG_NG << "first_time..." << (recorder.is_skipping() ? "skipping" : "no skip") << "\n"; + LOG_NG << "first_time..." << (is_skipping_replay() ? "skipping" : "no skip") << "\n"; events::raise_draw_event(); try { diff --git a/src/playturn.cpp b/src/playturn.cpp index 90ede4c8de63..4f153598e5c5 100644 --- a/src/playturn.cpp +++ b/src/playturn.cpp @@ -75,7 +75,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::sync_network() //could get data back pertaining to the next turn. config cfg; while( (retv == turn_info::PROCESS_CONTINUE) && network_reader_.read(cfg)) { - retv = process_network_data(cfg,false); + retv = process_network_data(cfg); cfg.clear(); } send_data(); @@ -92,20 +92,15 @@ void turn_info::send_data() } } -turn_info::PROCESS_DATA_RESULT turn_info::handle_turn( - const config& t, - const bool skip_replay) +turn_info::PROCESS_DATA_RESULT turn_info::handle_turn(const config& t) { //t can contain a [command] or a [upload_log] assert(t.all_children_count() == 1); /** @todo FIXME: Check what commands we execute when it's our turn! */ - bool was_skipping = recorder.is_skipping(); - recorder.set_skip(skip_replay); //note, that this function might call itself recursively: do_replay -> ... -> persist_var -> ... -> handle_generic_event -> sync_network -> handle_turn recorder.add_config(t, replay::MARK_AS_SENT); PROCESS_DATA_RESULT retv = replay_to_process_data_result(do_replay()); - recorder.set_skip(was_skipping); return retv; } @@ -116,12 +111,12 @@ void turn_info::do_save() } } -turn_info::PROCESS_DATA_RESULT turn_info::process_network_data_from_reader(bool skip_replay) +turn_info::PROCESS_DATA_RESULT turn_info::process_network_data_from_reader() { config cfg; while(this->network_reader_.read(cfg)) { - PROCESS_DATA_RESULT res = process_network_data(cfg, skip_replay); + PROCESS_DATA_RESULT res = process_network_data(cfg); if(res != PROCESS_CONTINUE) { return res; @@ -130,7 +125,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data_from_reader(bool return PROCESS_CONTINUE; } -turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg, bool skip_replay) +turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg) { // we cannot be connected to multiple peers anymore because // the simple wesnothserver implementation in wesnoth was removed years ago. @@ -167,7 +162,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg } else if (const config &turn = cfg.child("turn")) { - return handle_turn(turn, skip_replay); + return handle_turn(turn); } else if (cfg.has_child("whiteboard")) { diff --git a/src/playturn.hpp b/src/playturn.hpp index d54a333f7a47..0480e91a4e26 100644 --- a/src/playturn.hpp +++ b/src/playturn.hpp @@ -50,10 +50,10 @@ class turn_info void send_data(); //function which will process incoming network data received with playturn_network_adapter, and act on it. - PROCESS_DATA_RESULT process_network_data(const config& cfg, bool skip_replay); + PROCESS_DATA_RESULT process_network_data(const config& cfg); //reads as much data from network_reader_ as possible and processed it. - PROCESS_DATA_RESULT process_network_data_from_reader(bool skip_replay); + PROCESS_DATA_RESULT process_network_data_from_reader(); events::generic_event& host_transfer() { return host_transfer_; } @@ -64,9 +64,7 @@ class turn_info static void change_controller(int side, const std::string& controller); static void change_side_controller(int side, const std::string& player); static PROCESS_DATA_RESULT replay_to_process_data_result(REPLAY_RETURN replayreturn); - PROCESS_DATA_RESULT handle_turn( - const config& t, - const bool skip_replay); + PROCESS_DATA_RESULT handle_turn(const config& t); void do_save(); diff --git a/src/replay.cpp b/src/replay.cpp index e81a4a0ee563..1efcd3ad38d4 100644 --- a/src/replay.cpp +++ b/src/replay.cpp @@ -186,14 +186,12 @@ chat_msg::~chat_msg() replay::replay() : cfg_(), pos_(0), - skip_(false), message_locations() {} replay::replay(const config& cfg) : cfg_(cfg), pos_(0), - skip_(false), message_locations() {} @@ -215,16 +213,6 @@ void replay::process_error(const std::string& msg) resources::controller->process_oos(msg); // might throw end_level_exception(QUIT) } -void replay::set_skip(bool skip) -{ - skip_ = skip; -} - -bool replay::is_skipping() const -{ - return skip_; -} - void replay::add_unit_checksum(const map_location& loc,config& cfg) { if(! game_config::mp_debug) { @@ -648,7 +636,6 @@ void replay::clear() message_log.clear(); cfg_ = config(); pos_ = 0; - skip_ = false; } bool replay::empty() @@ -721,11 +708,11 @@ REPLAY_RETURN do_replay(bool one_move) { log_scope("do replay"); - if (!get_replay_source().is_skipping()){ + if (!resources::controller->is_skipping_replay()) { resources::screen->recalculate_minimap(); } - update_locker lock_update(resources::screen->video(),get_replay_source().is_skipping()); + update_locker lock_update(resources::screen->video(), resources::controller->is_skipping_replay()); return do_replay_handle(one_move); } @@ -769,7 +756,7 @@ REPLAY_RETURN do_replay_handle(bool one_move) //if (!preferences::parse_should_show_lobby_join(speaker_name, message)) return; bool is_whisper = (speaker_name.find("whisper: ") == 0); get_replay_source().add_chat_message_location(); - if (!get_replay_source().is_skipping() || is_whisper) { + if (!resources::controller->is_skipping_replay() || is_whisper) { int side = child["side"]; resources::screen->get_chat_manager().add_chat_message(get_time(child), speaker_name, side, message, (team_name.empty() ? events::chat_handler::MESSAGE_PUBLIC @@ -905,7 +892,7 @@ REPLAY_RETURN do_replay_handle(bool one_move) /* we need to use the undo stack during replays in order to make delayed shroud updated work. */ - synced_context::run_in_synced_context(commandname, data, true, !get_replay_source().is_skipping(), false,show_oos_error_error_function); + synced_context::run_in_synced_context(commandname, data, true, !resources::controller->is_skipping_replay(), false,show_oos_error_error_function); if (one_move) { return REPLAY_FOUND_END_MOVE; } diff --git a/src/replay.hpp b/src/replay.hpp index 1d3c471fd4f7..557a0d1eefa3 100644 --- a/src/replay.hpp +++ b/src/replay.hpp @@ -55,9 +55,6 @@ class replay void append(const config& cfg); - void set_skip(bool skip); - bool is_skipping() const; - void add_start(); void add_countdown_update(int value,int team); @@ -152,8 +149,6 @@ class replay config cfg_; int pos_; - bool skip_; - std::vector message_locations; }; diff --git a/src/replay_controller.cpp b/src/replay_controller.cpp index fe7768bf4253..79be39997ad7 100644 --- a/src/replay_controller.cpp +++ b/src/replay_controller.cpp @@ -324,7 +324,6 @@ void replay_controller::reset_replay() skip_replay_ = false; gamestate_.tod_manager_= tod_manager_start_; recorder.start_replay(); - recorder.set_skip(false); saved_game_ = saved_game_start_; gamestate_.board_ = gameboard_start_; gui_->change_display_context(&gamestate_.board_); //this doesn't change the pointer value, but it triggers the gui to update the internal terrain builder object, @@ -400,7 +399,7 @@ possible_end_play_signal replay_controller::replay_next_turn(){ PROPOGATE_END_PLAY_SIGNAL( play_turn() ); - if (!skip_replay_ || !is_playing_){ + if (!is_skipping_replay() || !is_playing_){ gui_->scroll_to_leader(player_number_,game_display::ONSCREEN,false); } @@ -417,7 +416,7 @@ possible_end_play_signal replay_controller::replay_next_move_or_side(bool one_mo HANDLE_END_PLAY_SIGNAL( play_move_or_side(one_move) ); } - if ( (!skip_replay_ || !is_playing_) && (last_replay_action == REPLAY_FOUND_END_TURN) ){ + if ( (!is_skipping_replay() || !is_playing_) && (last_replay_action == REPLAY_FOUND_END_TURN) ){ gui_->scroll_to_leader(player_number_,game_display::ONSCREEN,false); } @@ -476,7 +475,6 @@ void replay_controller::replay_show_team1(){ void replay_controller::replay_skip_animation(){ skip_replay_ = !skip_replay_; - recorder.set_skip(skip_replay_); } //move all sides till stop/end @@ -644,7 +642,7 @@ void replay_controller::handle_generic_event(const std::string& name){ gui::button* skip_animation_button = gui_->find_action_button("skip-animation"); if(skip_animation_button) { - skip_animation_button->set_check(skip_replay_); + skip_animation_button->set_check(is_skipping_replay()); } } else { rebuild_replay_theme(); From 180acb077cc220a43b5f19473e2d8b9fab054973 Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sat, 28 Feb 2015 19:16:04 +0100 Subject: [PATCH 03/10] don't do auto delete save on defeat in mp campaigns We now use 'end_level.proceed_to_next_level' instead of 'res == VICTORY || (game_type == game_classification::MULTIPLAYER && res == DEFEAT' to decide whether we do auto save deletion. The intention is to adress 'if MP campaigns ever work again, we might need to change this test' as it was commented in the code We also refactor playsingle/mp_scenario to not do playcontroller.update_savegame_snapshot(); if res == DEFEAT since it is not neeeded in this case. --- src/game_initialization/playcampaign.cpp | 99 +++++++++++------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/src/game_initialization/playcampaign.cpp b/src/game_initialization/playcampaign.cpp index c43895112148..13ceba78ece5 100644 --- a/src/game_initialization/playcampaign.cpp +++ b/src/game_initialization/playcampaign.cpp @@ -190,26 +190,26 @@ static LEVEL_RESULT playsingle_scenario(const config& game_config, end_level = playcontroller.get_end_level_data_const(); - if (res != QUIT) + if (res == QUIT) { - //if we are loading from linger mode then we already did this. - if(res != SKIP_TO_LINGER) - { - show_carryover_message(state_of_game, playcontroller, disp, end_level, res); - } - if(!disp.video().faked()) - { - try { - playcontroller.maybe_linger(); - } catch(end_level_exception& e) { - if (e.result == QUIT) { - return QUIT; - } + return QUIT; + } + //if we are loading from linger mode then we already did this. + if(res != SKIP_TO_LINGER) + { + show_carryover_message(state_of_game, playcontroller, disp, end_level, res); + } + if(!disp.video().faked()) + { + try { + playcontroller.maybe_linger(); + } catch(end_level_exception& e) { + if (e.result == QUIT) { + return QUIT; } } } state_of_game.set_snapshot(playcontroller.to_config()); - return res; } @@ -232,25 +232,25 @@ static LEVEL_RESULT playmp_scenario(const config& game_config, if (io_type == IO_CLIENT && playcontroller.is_host()) io_type = IO_SERVER; - if (res != QUIT) + if (res == QUIT) { - if(res != OBSERVER_END && res != SKIP_TO_LINGER) - { - //We need to call this before linger because it prints the defeated/victory message. - //(we want to see that message before entering the linger mode) - show_carryover_message(state_of_game, playcontroller, disp, end_level, res); - } - if(!disp.video().faked()) - { - try { - playcontroller.maybe_linger(); - } catch(end_level_exception& e) { - if (e.result == QUIT) { - return QUIT; - } + return QUIT; + } + if(res != OBSERVER_END && res != SKIP_TO_LINGER) + { + //We need to call this before linger because it prints the defeated/victory message. + //(we want to see that message before entering the linger mode) + show_carryover_message(state_of_game, playcontroller, disp, end_level, res); + } + if(!disp.video().faked()) + { + try { + playcontroller.maybe_linger(); + } catch(end_level_exception& e) { + if (e.result == QUIT) { + return QUIT; } } - } playcontroller.update_savegame_snapshot(); return res; @@ -317,40 +317,35 @@ LEVEL_RESULT play_game(game_display& disp, saved_game& gamestate, if (is_unit_test) { return res; } - //in this case we might have skipped state.set_snapshot which means we cannot do gamestate.convert_to_start_save(); - if(res == QUIT) - { + if(res == QUIT) { return res; } - - // Save-management options fire on game end. - // This means: (a) we have a victory, or - // (b) we're multiplayer live, in which - // case defeat is also game end. Someday, - // if MP campaigns ever work again, we might - // need to change this test. - if (res == VICTORY || (game_type == game_classification::MULTIPLAYER && res == DEFEAT)) { - if (preferences::delete_saves()) - savegame::clean_saves(gamestate.classification().label); - - if (preferences::save_replays() && end_level.replay_save) { - savegame::replay_savegame save(gamestate, preferences::save_compression_format()); - save.save_game_automatic(disp.video(), true); - } + // proceed_to_next_level <=> 'any human side recieved victory' + // If 'any human side recieved victory' we do the Save-management options + // Otherwise we are done now + if(!end_level.proceed_to_next_level) { + return res; } + if (preferences::delete_saves()) { + savegame::clean_saves(gamestate.classification().label); + } + if (preferences::save_replays() && end_level.replay_save) { + savegame::replay_savegame save(gamestate, preferences::save_compression_format()); + save.save_game_automatic(disp.video(), true); + } + gamestate.convert_to_start_save(); recorder.clear(); - // On DEFEAT, QUIT, or OBSERVER_END, we're done now - //If there is no next scenario we're done now. - if(!end_level.proceed_to_next_level || gamestate.get_scenario_id().empty()) + if(gamestate.get_scenario_id().empty()) { return res; } else if(res == OBSERVER_END) { + // TODO: does it make sense to ask this question if we are currently the host? const int dlg_res = gui2::show_message(disp.video(), _("Game Over"), _("This scenario has ended. Do you want to continue the campaign?"), gui2::tmessage::yes_no_buttons); From 107da1f240a4c9d55382a52ce5936771bc7b7cac Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sat, 28 Feb 2015 19:39:47 +0100 Subject: [PATCH 04/10] refactor play_controller::fire_start We remove a try/ctach for an exception that cannot be thrown. We move the random mode wanring out of fire_start() Also we use gui2::show_transient_message instead of replay::process_error becasue the error is no OOS. --- src/play_controller.cpp | 15 +++------------ src/playsingle_controller.cpp | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/play_controller.cpp b/src/play_controller.cpp index 0ef6bde3c846..292260b29c6b 100644 --- a/src/play_controller.cpp +++ b/src/play_controller.cpp @@ -37,6 +37,7 @@ #include "hotkey_handler.hpp" #include "map_label.hpp" #include "gettext.hpp" +#include "gui/dialogs/transient_message.hpp" #include "halo.hpp" #include "hotkey/command_executor.hpp" #include "loadscreen.hpp" @@ -318,20 +319,10 @@ void play_controller::fire_start(bool execute){ init_side_done_ = false; } else { + // FIXME: calculate it_is_a_new_turn_ correctly instead of setting it to false when reloading a game. + // it could cause missed turn events in case init_side_done_ == false. it_is_a_new_turn_ = false; } - if( saved_game_.classification().random_mode != "" && (network::nconnections() != 0)) - { - std::string mes = _("MP game uses an alternative random mode, if you don't know what this message means, then most likeley someone is cheating or someone reloaded a corrupt game."); - gui_->get_chat_manager().add_chat_message( - time(NULL), - "game_engine", - 0, - mes, - events::chat_handler::MESSAGE_PUBLIC, - preferences::message_bell()); - replay::process_error(mes); - } gamestate_.gamedata_.set_phase(game_data::PLAY); } diff --git a/src/playsingle_controller.cpp b/src/playsingle_controller.cpp index da162fcd548d..4c7768421d7b 100644 --- a/src/playsingle_controller.cpp +++ b/src/playsingle_controller.cpp @@ -258,16 +258,18 @@ boost::optional playsingle_controller::play_scenario_init(end_leve init_gui(); past_prestart = true; events::raise_draw_event(); - try { - fire_start(false); - } catch (end_level_exception & e) { - return e.result; - } catch (restart_turn_exception &) { - assert(false && "caugh end_turn exception in a bad place... terminating."); - std::terminate(); - } + fire_start(false); gui_->recalculate_minimap(); } + if( saved_game_.classification().random_mode != "" && (network::nconnections() != 0)) { + // This won't cause errors later but we should notify the user about it in case he didn't knew it. + gui2::show_transient_message( + gui_->video(), + // TODO: find a better title + _("Game Error"), + _("This multiplayer game uses an alternative random mode, if you don't know what this message means, then most likeley someone is cheating or someone reloaded a corrupt game.") + ); + } return boost::none; } From 560a4c9d72f6807d6fea8fe45e470e5ea2a52baa Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sat, 28 Feb 2015 19:50:46 +0100 Subject: [PATCH 05/10] remove 'past_prestart' variable --- src/playsingle_controller.cpp | 14 +++++--------- src/playsingle_controller.hpp | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/playsingle_controller.cpp b/src/playsingle_controller.cpp index 4c7768421d7b..e299c9b29486 100644 --- a/src/playsingle_controller.cpp +++ b/src/playsingle_controller.cpp @@ -190,7 +190,7 @@ void playsingle_controller::report_victory( report << '\n' << goldmsg; } -boost::optional playsingle_controller::play_scenario_init(end_level_data & /*eld*/, bool & past_prestart) { +boost::optional playsingle_controller::play_scenario_init(end_level_data & /*eld*/) { // At the beginning of the scenario, save a snapshot as replay_start if(saved_game_.replay_start().empty()){ saved_game_.replay_start() = to_config(); @@ -238,7 +238,6 @@ boost::optional playsingle_controller::play_scenario_init(end_leve init_gui(); - past_prestart = true; LOG_NG << "first_time..." << (is_skipping_replay() ? "skipping" : "no skip") << "\n"; events::raise_draw_event(); @@ -256,7 +255,6 @@ boost::optional playsingle_controller::play_scenario_init(end_leve else { init_gui(); - past_prestart = true; events::raise_draw_event(); fire_start(false); gui_->recalculate_minimap(); @@ -273,7 +271,7 @@ boost::optional playsingle_controller::play_scenario_init(end_leve return boost::none; } -LEVEL_RESULT playsingle_controller::play_scenario_main_loop(end_level_data & end_level, bool & /*past_prestart*/) { +LEVEL_RESULT playsingle_controller::play_scenario_main_loop(end_level_data & end_level) { LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks_) << "\n"; // Initialize countdown clock. @@ -353,21 +351,19 @@ LEVEL_RESULT playsingle_controller::play_scenario( set_remove_from_carryover_on_defeat(level_["remove_from_carryover_on_defeat"].to_bool(true)); end_level_data &end_level = get_end_level_data(); - bool past_prestart = false; - LOG_NG << "entering try... " << (SDL_GetTicks() - ticks_) << "\n"; try { - boost::optional signal = play_scenario_init(end_level, past_prestart); + boost::optional signal = play_scenario_init(end_level); if (!signal) { - signal = play_scenario_main_loop(end_level, past_prestart); + signal = play_scenario_main_loop(end_level); } assert(signal); //play_scenario_main_loop always returns a LEVEL_RESULT { LEVEL_RESULT end_level_result = *signal; - if(!past_prestart) { + if(!this->gamestate_.gamedata_.phase() > game_data::PRESTART) { sdl::draw_solid_tinted_rectangle( 0, 0, gui_->video().getx(), gui_->video().gety(), 0, 0, 0, 1.0, gui_->video().getSurface() diff --git a/src/playsingle_controller.hpp b/src/playsingle_controller.hpp index 281d514593dd..f6b05c8d7a8b 100644 --- a/src/playsingle_controller.hpp +++ b/src/playsingle_controller.hpp @@ -49,8 +49,8 @@ class playsingle_controller : public play_controller LEVEL_RESULT play_scenario(const config::const_child_itors &story, bool skip_replay); - boost::optional play_scenario_init(end_level_data & eld, bool & past_prestart ); - LEVEL_RESULT play_scenario_main_loop(end_level_data & eld, bool & past_prestart ); + boost::optional play_scenario_init(end_level_data & eld); + LEVEL_RESULT play_scenario_main_loop(end_level_data & eld); virtual void handle_generic_event(const std::string& name); From bf373658e5720616371dddd41608642b9f99b79a Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sat, 28 Feb 2015 19:56:25 +0100 Subject: [PATCH 06/10] store it_is_a_new_turn_ in savefiles this is intended to fix a case where turn X events are not fired when reloading init_side_done=no saves when it is the first players turn. --- src/play_controller.cpp | 7 ++----- src/replay_controller.cpp | 2 -- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/play_controller.cpp b/src/play_controller.cpp index 292260b29c6b..05b59faee566 100644 --- a/src/play_controller.cpp +++ b/src/play_controller.cpp @@ -138,7 +138,7 @@ play_controller::play_controller(const config& level, saved_game& state_of_game, , start_turn_(gamestate_.tod_manager_.turn()) // gamestate_.tod_manager_ constructed above , skip_replay_(skip_replay) , linger_(false) - , it_is_a_new_turn_(true) + , it_is_a_new_turn_(level["it_is_a_new_turn"].to_bool(true)) , init_side_done_(level["init_side_done"].to_bool(false)) , ticks_(ticks) , victory_when_enemies_defeated_(true) @@ -318,10 +318,6 @@ void play_controller::fire_start(bool execute){ } init_side_done_ = false; - } else { - // FIXME: calculate it_is_a_new_turn_ correctly instead of setting it to false when reloading a game. - // it could cause missed turn events in case init_side_done_ == false. - it_is_a_new_turn_ = false; } gamestate_.gamedata_.set_phase(game_data::PLAY); } @@ -454,6 +450,7 @@ config play_controller::to_config() const cfg.merge_attributes(level_); cfg["init_side_done"] = init_side_done_; + cfg["it_is_a_new_turn"] = it_is_a_new_turn_; gamestate_.write(cfg); diff --git a/src/replay_controller.cpp b/src/replay_controller.cpp index 79be39997ad7..2b288e9c6f21 100644 --- a/src/replay_controller.cpp +++ b/src/replay_controller.cpp @@ -382,8 +382,6 @@ void replay_controller::reset_replay() fire_start(true); sync.do_final_checkup(); } - // Since we did not fire the start event, it_is_a_new_turn_ has the wrong value. - it_is_a_new_turn_ = true; update_gui(); reset_replay_ui(); From 5ac4fbae91ca9b5d168c3d8e86b3ba761577639a Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sat, 28 Feb 2015 19:58:47 +0100 Subject: [PATCH 07/10] refactor play_controller::fire_start --- src/play_controller.cpp | 30 ++++++++++++++---------------- src/play_controller.hpp | 2 +- src/playsingle_controller.cpp | 6 +++--- src/replay_controller.cpp | 2 +- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/play_controller.cpp b/src/play_controller.cpp index 05b59faee566..1a12897ff809 100644 --- a/src/play_controller.cpp +++ b/src/play_controller.cpp @@ -301,24 +301,22 @@ void play_controller::fire_prestart() gamestate_.gamedata_.get_variable("turn_number") = int(start_turn_); } -void play_controller::fire_start(bool execute){ - if(execute) { - gamestate_.gamedata_.set_phase(game_data::START); - pump().fire("start"); - check_end_level(); - // start event may modify start turn with WML, reflect any changes. - start_turn_ = turn(); - gamestate_.gamedata_.get_variable("turn_number") = int(start_turn_); - - // prestart and start events may modify the initial gold amount, - // reflect any changes. - BOOST_FOREACH(team& tm, gamestate_.board_.teams_) - { - tm.set_start_gold(tm.gold()); - } - init_side_done_ = false; +void play_controller::fire_start() +{ + gamestate_.gamedata_.set_phase(game_data::START); + pump().fire("start"); + check_end_level(); + // start event may modify start turn with WML, reflect any changes. + start_turn_ = turn(); + gamestate_.gamedata_.get_variable("turn_number") = int(start_turn_); + // prestart and start events may modify the initial gold amount, + // reflect any changes. + BOOST_FOREACH(team& tm, gamestate_.board_.teams_) + { + tm.set_start_gold(tm.gold()); } + init_side_done_ = false; gamestate_.gamedata_.set_phase(game_data::PLAY); } diff --git a/src/play_controller.hpp b/src/play_controller.hpp index bdacb9d34ad6..4e9f9d2fdaea 100644 --- a/src/play_controller.hpp +++ b/src/play_controller.hpp @@ -215,7 +215,7 @@ class play_controller : public controller_base, public events::observer, public ///preload events cannot be synced void fire_preload(); void fire_prestart(); - void fire_start(bool execute); + void fire_start(); virtual void init_gui(); virtual void finish_side_turn(); void finish_turn(); //this should not throw an end turn or end level exception diff --git a/src/playsingle_controller.cpp b/src/playsingle_controller.cpp index e299c9b29486..34ea1df4c98a 100644 --- a/src/playsingle_controller.cpp +++ b/src/playsingle_controller.cpp @@ -242,7 +242,7 @@ boost::optional playsingle_controller::play_scenario_init(end_leve events::raise_draw_event(); try { - fire_start(true); + fire_start(); } catch (end_level_exception & e) { return e.result; } catch (restart_turn_exception &) { @@ -256,7 +256,7 @@ boost::optional playsingle_controller::play_scenario_init(end_leve { init_gui(); events::raise_draw_event(); - fire_start(false); + gamestate_.gamedata_.set_phase(game_data::PLAY); gui_->recalculate_minimap(); } if( saved_game_.classification().random_mode != "" && (network::nconnections() != 0)) { @@ -363,7 +363,7 @@ LEVEL_RESULT playsingle_controller::play_scenario( { LEVEL_RESULT end_level_result = *signal; - if(!this->gamestate_.gamedata_.phase() > game_data::PRESTART) { + if(this->gamestate_.gamedata_.phase() <= game_data::PRESTART) { sdl::draw_solid_tinted_rectangle( 0, 0, gui_->video().getx(), gui_->video().gety(), 0, 0, 0, 1.0, gui_->video().getSurface() diff --git a/src/replay_controller.cpp b/src/replay_controller.cpp index 2b288e9c6f21..6dc24237c5fb 100644 --- a/src/replay_controller.cpp +++ b/src/replay_controller.cpp @@ -379,7 +379,7 @@ void replay_controller::reset_replay() fire_prestart(); init_gui(); - fire_start(true); + fire_start(); sync.do_final_checkup(); } update_gui(); From 30e670f5a8babd17fa5647d1ef12176a04be44de Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sat, 28 Feb 2015 23:38:48 +0100 Subject: [PATCH 08/10] refactor end_level_data, end_level_exception and LEVEL_RESULT I removed the NONE value of LEVEL_RESULT because 'LEVEL_RESULT==NONE' was basicly the same information as '!end_level_data.transient.disabled' I removed end_level_data.transient.disabled and instead store the end level_data as an optional to remember whether we already have an end_level_data I removed the SKIP_TO_LINGER value of LEVEL_RESULT because it was not possible that play_scenario returned SKIP_TO_LINGER (so i removed that code in playcampaign.cpp that handled this case) I replaced playsingle_controller::level_result_ and end_level_exception/struct::result which both held more or less the same data with end_level_data::is_vitory, in case of QUIT the optional is empty (none_t) I also reordered the if cases in playsingle_controller::play_scenario to simplify. --- src/game_end_exceptions.cpp | 1 - src/game_end_exceptions.hpp | 37 ++-- src/game_initialization/playcampaign.cpp | 33 ++-- src/game_launcher.cpp | 6 +- src/hotkey/command_executor.cpp | 4 +- src/menu_events.cpp | 14 +- src/play_controller.cpp | 27 +-- src/play_controller.hpp | 18 +- src/playmp_controller.cpp | 6 +- src/playsingle_controller.cpp | 208 +++++++++++------------ src/playsingle_controller.hpp | 9 +- src/playturn.cpp | 2 +- src/replay_controller.cpp | 14 +- src/replay_controller.hpp | 1 - src/savegame.cpp | 2 +- src/scripting/game_lua_kernel.cpp | 19 +-- 16 files changed, 193 insertions(+), 208 deletions(-) diff --git a/src/game_end_exceptions.cpp b/src/game_end_exceptions.cpp index fb056db41d3e..b77a305c6355 100644 --- a/src/game_end_exceptions.cpp +++ b/src/game_end_exceptions.cpp @@ -23,7 +23,6 @@ transient_end_level::transient_end_level() , linger_mode(true) , custom_endlevel_music() , reveal_map(true) - , disabled(false) {} end_level_data::end_level_data() diff --git a/src/game_end_exceptions.hpp b/src/game_end_exceptions.hpp index 3abb6c7d0541..1b35588bf755 100644 --- a/src/game_end_exceptions.hpp +++ b/src/game_end_exceptions.hpp @@ -33,12 +33,10 @@ #include MAKE_ENUM(LEVEL_RESULT, - (NONE, "none") (VICTORY, "victory") (DEFEAT, "defeat") (QUIT, "quit") (OBSERVER_END, "observer_end") - (SKIP_TO_LINGER,"skip_to_linger") ) MAKE_ENUM_STREAM_OPS1(LEVEL_RESULT) @@ -96,7 +94,7 @@ class restart_turn_exception * Struct used to transmit info caught from an end_turn_exception. */ struct end_level_struct { - LEVEL_RESULT result; + bool is_quit; }; /** @@ -108,17 +106,17 @@ class end_level_exception { public: - end_level_exception(LEVEL_RESULT res) + end_level_exception(bool isquit = false) : tlua_jailbreak_exception() , std::exception() - , result(res) + , is_quit(isquit) { } - LEVEL_RESULT result; + bool is_quit; end_level_struct to_struct() { - end_level_struct els = {result}; + end_level_struct els = {is_quit}; return els; } @@ -188,20 +186,6 @@ class get_signal_type : public boost::static_visitor { } }; -class get_result : public boost::static_visitor { -public: - LEVEL_RESULT operator()(restart_turn_struct & ) const - { - return NONE; - } - - LEVEL_RESULT operator()(end_level_struct & s) const - { - return s.result; - } -}; - - /** * The non-persistent part of end_level_data */ @@ -213,7 +197,6 @@ struct transient_end_level{ bool linger_mode; /**< Should linger mode be invoked? */ std::string custom_endlevel_music; /**< Custom short music played at the end. */ bool reveal_map; /**< Should we reveal map when game is ended? (Multiplayer only) */ - bool disabled; /**< Limits execution of tag [endlevel] to a single time > */ }; /** @@ -228,7 +211,7 @@ struct end_level_data bool replay_save; /**< Should a replay save be made? */ bool proceed_to_next_level; /**< whether to proceed to the next scenario, equals (res == VICTORY) in sp. We need to save this in saves during linger mode. > */ transient_end_level transient; - + bool is_victory; void write(config& cfg) const; void read(const config& cfg); @@ -240,5 +223,11 @@ struct end_level_data return r; } }; - +inline void throw_quit_game_exception() +{ + // Distinguish 'Quit' from 'Regular' end_level_exceptions to solve the following problem: + // If a player quits the game during an event after an [endlevel] occurs, the game won't + // Quit but continue with the [endlevel] instead. + throw end_level_exception(true); +} #endif /* ! GAME_END_EXCEPTIONS_HPP_INCLUDED */ diff --git a/src/game_initialization/playcampaign.cpp b/src/game_initialization/playcampaign.cpp index 13ceba78ece5..a88ffe2b3252 100644 --- a/src/game_initialization/playcampaign.cpp +++ b/src/game_initialization/playcampaign.cpp @@ -171,7 +171,8 @@ LEVEL_RESULT play_replay(display& disp, saved_game& gamestate, const config& gam e.show(disp); } } - return NONE; + //TODO: when can this happen? + return VICTORY; } static LEVEL_RESULT playsingle_scenario(const config& game_config, @@ -188,25 +189,20 @@ static LEVEL_RESULT playsingle_scenario(const config& game_config, LEVEL_RESULT res = playcontroller.play_scenario(story, skip_replay); - end_level = playcontroller.get_end_level_data_const(); - if (res == QUIT) { return QUIT; } - //if we are loading from linger mode then we already did this. - if(res != SKIP_TO_LINGER) - { - show_carryover_message(state_of_game, playcontroller, disp, end_level, res); - } + + end_level = playcontroller.get_end_level_data_const(); + + show_carryover_message(state_of_game, playcontroller, disp, end_level, res); if(!disp.video().faked()) { try { playcontroller.maybe_linger(); - } catch(end_level_exception& e) { - if (e.result == QUIT) { - return QUIT; - } + } catch(end_level_exception&) { + return QUIT; } } state_of_game.set_snapshot(playcontroller.to_config()); @@ -226,8 +222,6 @@ static LEVEL_RESULT playmp_scenario(const config& game_config, game_config, tdata, disp.video(), skip_replay, blindfold_replay, io_type == IO_SERVER); LEVEL_RESULT res = playcontroller.play_scenario(story, skip_replay); - end_level = playcontroller.get_end_level_data_const(); - //Check if the player started as mp client and changed to host if (io_type == IO_CLIENT && playcontroller.is_host()) io_type = IO_SERVER; @@ -236,7 +230,10 @@ static LEVEL_RESULT playmp_scenario(const config& game_config, { return QUIT; } - if(res != OBSERVER_END && res != SKIP_TO_LINGER) + + end_level = playcontroller.get_end_level_data_const(); + + if(res != OBSERVER_END) { //We need to call this before linger because it prints the defeated/victory message. //(we want to see that message before entering the linger mode) @@ -246,10 +243,8 @@ static LEVEL_RESULT playmp_scenario(const config& game_config, { try { playcontroller.maybe_linger(); - } catch(end_level_exception& e) { - if (e.result == QUIT) { - return QUIT; - } + } catch(end_level_exception&) { + return QUIT; } } playcontroller.update_savegame_snapshot(); diff --git a/src/game_launcher.cpp b/src/game_launcher.cpp index 6b0dea768007..9d8124effb6f 100644 --- a/src/game_launcher.cpp +++ b/src/game_launcher.cpp @@ -593,7 +593,7 @@ int game_launcher::unit_test() try { LEVEL_RESULT res = play_game(disp(),state_,game_config_manager::get()->game_config(), game_config_manager::get()->terrain_types(), IO_SERVER, false, false, false, true); - if (!(res == VICTORY || res == NONE) || lg::broke_strict()) { + if (!(res == VICTORY) || lg::broke_strict()) { return 1; } } catch (game::load_game_exception &) { @@ -629,12 +629,10 @@ int game_launcher::unit_test() try { //LEVEL_RESULT res = play_game(disp(), state_, game_config_manager::get()->game_config(), IO_SERVER, false,false,false,true); LEVEL_RESULT res = ::play_replay(disp(), state_, game_config_manager::get()->game_config(), game_config_manager::get()->terrain_types(), true); - if (!(res == VICTORY || res == NONE)) { + if (!(res == VICTORY)) { std::cerr << "Observed failure on replay" << std::endl; return 4; } - /*::play_replay(disp(),state_,game_config_manager::get()->game_config(), - video_);*/ clear_loaded_game(); } catch (game::load_game_exception &) { std::cerr << "Load_game_exception encountered during play_replay!" << std::endl; diff --git a/src/hotkey/command_executor.cpp b/src/hotkey/command_executor.cpp index 28af202ee5c7..1166cb598baf 100644 --- a/src/hotkey/command_executor.cpp +++ b/src/hotkey/command_executor.cpp @@ -522,7 +522,7 @@ void key_event(display& disp, const SDL_KeyboardEvent& event, command_executor* const int res = gui2::show_message(disp.video(), _("Quit"), _("Do you really want to quit?"), gui2::tmessage::yes_no_buttons); if(res != gui2::twindow::CANCEL) { - throw end_level_exception(QUIT); + throw_quit_game_exception(); } else { return; } @@ -693,7 +693,7 @@ void execute_command(display& disp, const hotkey_command& command, command_execu const int res = gui2::show_message(disp.video(), _("Quit"), _("Do you really want to quit?"), gui2::tmessage::yes_no_buttons); if (res != gui2::twindow::CANCEL) { - throw end_level_exception(QUIT); + throw_quit_game_exception(); } } break; diff --git a/src/menu_events.cpp b/src/menu_events.cpp index 0f7a221f5295..a8cb851f32fc 100644 --- a/src/menu_events.cpp +++ b/src/menu_events.cpp @@ -2950,7 +2950,7 @@ void console_handler::do_save_quit() { do_quit(); } void console_handler::do_quit() { - throw end_level_exception(QUIT); + throw_quit_game_exception(); } void console_handler::do_ignore_replay_errors() { game_config::ignore_replay_errors = (get_data() != "off") ? true : false; @@ -2963,12 +2963,14 @@ void console_handler::do_next_level() { if (!get_data().empty()) menu_handler_.gamedata().set_next_scenario(get_data()); - end_level_data &e = menu_handler_.pc_.get_end_level_data(); + end_level_data e; e.transient.carryover_report = false; e.prescenario_save = true; e.transient.linger_mode = false; e.proceed_to_next_level = true; - throw end_level_exception(VICTORY); + e.is_victory = true; + menu_handler_.pc_.set_end_level_data(e); + throw end_level_exception(); } void console_handler::do_choose_level() { @@ -3011,12 +3013,14 @@ void console_handler::do_choose_level() { if (size_t(choice) < options.size()) { menu_handler_.gamedata().set_next_scenario(options[choice]); - end_level_data &e = menu_handler_.pc_.get_end_level_data(); + end_level_data e; e.transient.carryover_report = false; e.prescenario_save = true; e.transient.linger_mode = false; e.proceed_to_next_level = true; - throw end_level_exception(VICTORY); + e.is_victory = true; + menu_handler_.pc_.set_end_level_data(e); + throw end_level_exception(); } } diff --git a/src/play_controller.cpp b/src/play_controller.cpp index 1a12897ff809..3c202f22779c 100644 --- a/src/play_controller.cpp +++ b/src/play_controller.cpp @@ -163,6 +163,14 @@ play_controller::play_controller(const config& level, saved_game& state_of_game, resources::mp_settings = &saved_game_.mp_settings(); persist_.start_transaction(); + + if(const config& endlevel_cfg = level.child("end_level_data")) { + end_level_data el_data; + el_data.read(endlevel_cfg); + el_data.transient.carryover_report = false; + set_end_level_data(el_data); + } + n_unit::id_manager::instance().set_save_id(level_["next_underlying_unit_id"]); // Setup victory and defeat music @@ -185,7 +193,7 @@ play_controller::~play_controller() hotkey::delete_all_wml_hotkeys(); clear_resources(); } - +struct throw_end_level { void operator()(const config&) { throw_quit_game_exception(); } }; void play_controller::init(CVideo& video){ util::scoped_resource scoped_loadscreen_manager; loadscreen::global_loadscreen_manager* loadscreen_manager = loadscreen::global_loadscreen_manager::get(); @@ -205,9 +213,6 @@ void play_controller::init(CVideo& video){ whiteboard_manager_.reset(new wb::manager()); resources::whiteboard = whiteboard_manager_; - // mouse_handler expects at least one team for linger mode to work. - if (gamestate_.board_.teams().empty()) end_level_data_.transient.linger_mode = false; - LOG_NG << "loading units..." << (SDL_GetTicks() - ticks_) << std::endl; loadscreen::start_stage("load units"); preferences::encounter_all_content(gamestate_.board_); @@ -267,7 +272,7 @@ void play_controller::init(CVideo& video){ plugins_context_.reset(new plugins_context("Game")); plugins_context_->set_callback("save_game", boost::bind(&play_controller::save_game_auto, this, boost::bind(get_str, _1, "filename" )), true); plugins_context_->set_callback("save_replay", boost::bind(&play_controller::save_replay_auto, this, boost::bind(get_str, _1, "filename" )), true); - plugins_context_->set_callback("quit", boost::bind(&play_controller::force_end_level, this, QUIT), false); + plugins_context_->set_callback("quit", throw_end_level(), false); } void play_controller::init_managers(){ @@ -452,8 +457,8 @@ config play_controller::to_config() const gamestate_.write(cfg); - if(linger_) { - end_level_data_.write(cfg.add_child("end_level_data")); + if(end_level_data_.get_ptr() != NULL) { + end_level_data_->write(cfg.add_child("end_level_data")); } // Write terrain_graphics data in snapshot, too @@ -821,7 +826,6 @@ const std::string& play_controller::select_defeat_music() const return defeat_music_[rand() % defeat_music_.size()]; } - void play_controller::set_victory_music_list(const std::string& list) { victory_music_ = utils::split(list); @@ -885,8 +889,11 @@ void play_controller::check_victory() DBG_EE << "throwing end level exception..." << std::endl; //Also proceed to the next scenario when another player survived. - end_level_data_.proceed_to_next_level = found_player || found_network_player; - throw end_level_exception(found_player ? VICTORY : DEFEAT); + end_level_data el_data; + el_data.proceed_to_next_level = found_player || found_network_player; + el_data.is_victory = found_player; + set_end_level_data(el_data); + throw end_level_exception(); } void play_controller::process_oos(const std::string& msg) const diff --git a/src/play_controller.hpp b/src/play_controller.hpp index 4e9f9d2fdaea..ffdb0a7dc508 100644 --- a/src/play_controller.hpp +++ b/src/play_controller.hpp @@ -111,7 +111,6 @@ class play_controller : public controller_base, public events::observer, public void init_side_end(); virtual void force_end_turn() = 0; - virtual void force_end_level(LEVEL_RESULT res) = 0; virtual void check_end_level() = 0; virtual void on_not_observer() = 0; @@ -125,11 +124,18 @@ class play_controller : public controller_base, public events::observer, public { victory_when_enemies_defeated_ = e; } void set_remove_from_carryover_on_defeat(bool e) { remove_from_carryover_on_defeat_= e; } - end_level_data& get_end_level_data() { - return end_level_data_; + + void set_end_level_data(const end_level_data& data) { + end_level_data_ = data; + } + void reset_end_level_data() { + end_level_data_ = boost::none_t(); + } + bool is_regular_game_end() const { + return end_level_data_.get_ptr() != NULL; } const end_level_data& get_end_level_data_const() const { - return end_level_data_; + return *end_level_data_; } const std::vector& get_teams_const() const { return gamestate_.board_.teams_; @@ -279,7 +285,6 @@ class play_controller : public controller_base, public events::observer, public const std::string& select_victory_music() const; const std::string& select_defeat_music() const; - void set_victory_music_list(const std::string& list); void set_defeat_music_list(const std::string& list); @@ -294,7 +299,8 @@ class play_controller : public controller_base, public events::observer, public bool victory_when_enemies_defeated_; bool remove_from_carryover_on_defeat_; - end_level_data end_level_data_; + typedef boost::optional t_possible_end_level_data; + t_possible_end_level_data end_level_data_; std::vector victory_music_; std::vector defeat_music_; diff --git a/src/playmp_controller.cpp b/src/playmp_controller.cpp index cd8df332bc6e..f44e075cb16f 100644 --- a/src/playmp_controller.cpp +++ b/src/playmp_controller.cpp @@ -338,7 +338,7 @@ void playmp_controller::wait_for_upload() } else { - throw end_level_exception(QUIT); + throw_quit_game_exception(); } } catch(const end_level_exception&) { @@ -483,8 +483,8 @@ void playmp_controller::do_idle_notification() void playmp_controller::maybe_linger() { - linger_ = true; - if (!get_end_level_data_const().transient.linger_mode) { + // mouse_handler expects at least one team for linger mode to work. + if (!get_end_level_data_const().transient.linger_mode || gamestate_.board_.teams().empty()) { if(!is_host()) { // If we continue without lingering we need to // make sure the host uploads the next scenario diff --git a/src/playsingle_controller.cpp b/src/playsingle_controller.cpp index 34ea1df4c98a..55e25a7b61ac 100644 --- a/src/playsingle_controller.cpp +++ b/src/playsingle_controller.cpp @@ -56,6 +56,7 @@ #include "hotkey/hotkey_item.hpp" #include +#include static lg::log_domain log_aitesting("aitesting"); #define LOG_AIT LOG_STREAM(info, log_aitesting) @@ -83,25 +84,34 @@ playsingle_controller::playsingle_controller(const config& level, , replaying_(false) , skip_next_turn_(false) , do_autosaves_(false) - , level_result_(NONE) { hotkey_handler_.reset(new hotkey_handler(*this, saved_game_)); //upgrade hotkey handler to the sp (whiteboard enabled) version + // game may need to start in linger mode - if (state_of_game.classification().completion == "victory" || state_of_game.classification().completion == "defeat") - { - LOG_NG << "Setting linger mode.\n"; - linger_ = true; - } + linger_ = this->is_regular_game_end(); ai::game_info ai_info; ai::manager::set_ai_info(ai_info); ai::manager::add_observer(this) ; - plugins_context_->set_accessor_string("level_result", boost::bind(&LEVEL_RESULT_to_string, level_result_)); + plugins_context_->set_accessor_string("level_result", boost::bind(&playsingle_controller::describe_result, this)); plugins_context_->set_accessor_int("turn", boost::bind(&play_controller::turn, this)); } +std::string playsingle_controller::describe_result() const +{ + if(!is_regular_game_end()) { + return "NONE"; + } + else if(get_end_level_data_const().is_victory){ + return "VICTORY"; + } + else { + return "DEFEAT"; + } +} + playsingle_controller::~playsingle_controller() { ai::manager::remove_observer(this) ; @@ -190,7 +200,7 @@ void playsingle_controller::report_victory( report << '\n' << goldmsg; } -boost::optional playsingle_controller::play_scenario_init(end_level_data & /*eld*/) { +void playsingle_controller::play_scenario_init() { // At the beginning of the scenario, save a snapshot as replay_start if(saved_game_.replay_start().empty()){ saved_game_.replay_start() = to_config(); @@ -198,8 +208,11 @@ boost::optional playsingle_controller::play_scenario_init(end_leve try { fire_preload(); - } catch (end_level_exception & e) { - return e.result; + } catch (end_level_exception &e) { + if(e.is_quit) { + this->reset_end_level_data(); + } + return; } catch (restart_turn_exception &) { assert(false && "caugh end_turn exception in a bad place... terminating."); std::terminate(); @@ -229,9 +242,12 @@ boost::optional playsingle_controller::play_scenario_init(end_leve try { fire_prestart(); - } catch (end_level_exception & e) { - return e.result; - } catch (restart_turn_exception &) { + } catch (end_level_exception &e) { + if(e.is_quit) { + this->reset_end_level_data(); + } + return; + } catch (restart_turn_exception&) { assert(false && "caugh end_turn exception in a bad place... terminating."); std::terminate(); } @@ -243,11 +259,13 @@ boost::optional playsingle_controller::play_scenario_init(end_leve events::raise_draw_event(); try { fire_start(); - } catch (end_level_exception & e) { - return e.result; + } catch (end_level_exception &e) { + if(e.is_quit) { + this->reset_end_level_data(); + } + return; } catch (restart_turn_exception &) { - assert(false && "caugh end_turn exception in a bad place... terminating."); - std::terminate(); + //someone decided to droid a side during start event. Ignore it. } sync.do_final_checkup(); gui_->recalculate_minimap(); @@ -268,10 +286,10 @@ boost::optional playsingle_controller::play_scenario_init(end_leve _("This multiplayer game uses an alternative random mode, if you don't know what this message means, then most likeley someone is cheating or someone reloaded a corrupt game.") ); } - return boost::none; + return; } -LEVEL_RESULT playsingle_controller::play_scenario_main_loop(end_level_data & end_level) { +void playsingle_controller::play_scenario_main_loop() { LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks_) << "\n"; // Initialize countdown clock. @@ -282,16 +300,6 @@ LEVEL_RESULT playsingle_controller::play_scenario_main_loop(end_level_data & end } } - // if we loaded a save file in linger mode, skip to it. - if (linger_) { - //determine the bonus gold handling for this scenario - end_level.read(level_.child_or_empty("end_level_data")); - end_level.transient.carryover_report = false; - end_level.transient.disabled = true; - return SKIP_TO_LINGER; - //throw end_level_exception(SKIP_TO_LINGER); - } - // Avoid autosaving after loading, but still // allow the first turn to have an autosave. do_autosaves_ = !loading_game_; @@ -306,7 +314,10 @@ LEVEL_RESULT playsingle_controller::play_scenario_main_loop(end_level_data & end if (signal) { switch (boost::apply_visitor( get_signal_type(), *signal )) { case END_LEVEL: - return boost::apply_visitor( get_result(), *signal ); + if(boost::get(*signal).is_quit) { + reset_end_level_data(); + } + return; case END_TURN: assert(false && "end turn signal propagated to playsingle_controller::play_scenario_main_loop, terminating"); std::terminate(); @@ -349,19 +360,22 @@ LEVEL_RESULT playsingle_controller::play_scenario( set_victory_when_enemies_defeated(level_["victory_when_enemies_defeated"].to_bool(true)); set_remove_from_carryover_on_defeat(level_["remove_from_carryover_on_defeat"].to_bool(true)); - end_level_data &end_level = get_end_level_data(); LOG_NG << "entering try... " << (SDL_GetTicks() - ticks_) << "\n"; try { - boost::optional signal = play_scenario_init(end_level); - - if (!signal) { - signal = play_scenario_main_loop(end_level); + play_scenario_init(); + if (!is_regular_game_end() && !linger_) { + play_scenario_main_loop(); } - assert(signal); //play_scenario_main_loop always returns a LEVEL_RESULT { - LEVEL_RESULT end_level_result = *signal; + if (game_config::exit_at_end) { + exit(0); + } + if (!is_regular_game_end()) { + return QUIT; + } + const bool is_victory = get_end_level_data_const().is_victory; if(this->gamestate_.gamedata_.phase() <= game_data::PRESTART) { sdl::draw_solid_tinted_rectangle( @@ -372,8 +386,10 @@ LEVEL_RESULT playsingle_controller::play_scenario( } ai_testing::log_game_end(); + + const end_level_data& end_level = get_end_level_data_const(); if (!end_level.transient.custom_endlevel_music.empty()) { - if (end_level_result == DEFEAT) { + if (!is_victory) { set_defeat_music_list(end_level.transient.custom_endlevel_music); } else { set_victory_music_list(end_level.transient.custom_endlevel_music); @@ -387,52 +403,44 @@ LEVEL_RESULT playsingle_controller::play_scenario( return VICTORY; // this is probably only a story scenario, i.e. has its endlevel in the prestart event } - const bool obs = is_observer(); - if (game_config::exit_at_end) { - exit(0); - } - if (end_level_result == DEFEAT || end_level_result == VICTORY) - { - saved_game_.classification().completion = (end_level_result == VICTORY) ? "victory" : "defeat"; - // If we're a player, and the result is victory/defeat, then send - // a message to notify the server of the reason for the game ending. - if (!obs) { - config cfg; - config& info = cfg.add_child("info"); - info["type"] = "termination"; - info["condition"] = "game over"; - info["result"] = saved_game_.classification().completion; - network::send_data(cfg, 0); - } else { - gui2::show_transient_message(gui_->video(),_("Game Over"), - _("The game is over.")); - return OBSERVER_END; + if(linger_) { + LOG_NG << "resuming from loaded linger state...\n"; + //as carryover information is stored in the snapshot, we have to re-store it after loading a linger state + saved_game_.set_snapshot(config()); + if(!is_observer()) { + persist_.end_transaction(); } + return VICTORY; } - - if (end_level_result == QUIT) { - return QUIT; + saved_game_.classification().completion = is_victory ? "victory" : "defeat"; + if(is_observer()) { + gui2::show_transient_message(gui_->video(), _("Game Over"), _("The game is over.")); + return OBSERVER_END; + } + // If we're a player, and the result is victory/defeat, then send + // a message to notify the server of the reason for the game ending. + { + config cfg; + config& info = cfg.add_child("info"); + info["type"] = "termination"; + info["condition"] = "game over"; + info["result"] = saved_game_.classification().completion; + network::send_data(cfg, 0); } - else if (end_level_result == DEFEAT) + if (!is_victory) { saved_game_.classification().completion = "defeat"; pump().fire("defeat"); - if (!obs) { - const std::string& defeat_music = select_defeat_music(); - if(defeat_music.empty() != true) - sound::play_music_once(defeat_music); - persist_.end_transaction(); - return DEFEAT; - } else { - return QUIT; - } + const std::string& defeat_music = select_defeat_music(); + if(defeat_music.empty() != true) + sound::play_music_once(defeat_music); + persist_.end_transaction(); + return DEFEAT; } - else if (end_level_result == VICTORY) + else { - saved_game_.classification().completion = - - !end_level.transient.linger_mode ? "running" : "victory"; + saved_game_.classification().completion = !end_level.transient.linger_mode ? "running" : "victory"; pump().fire("victory"); // @@ -444,7 +452,7 @@ LEVEL_RESULT playsingle_controller::play_scenario( // a victory, so let them use [music] tags // instead should they want special music. // - if (!obs && end_level.transient.linger_mode) { + if (end_level.transient.linger_mode) { const std::string& victory_music = select_victory_music(); if(victory_music.empty() != true) sound::play_music_once(victory_music); @@ -454,23 +462,10 @@ LEVEL_RESULT playsingle_controller::play_scenario( gamestate_.board_.heal_all_survivors(); saved_game_.remove_snapshot(); - if(!is_observer()) { - persist_.end_transaction(); - } - + persist_.end_transaction(); return VICTORY; } - else if (end_level_result == SKIP_TO_LINGER) - { - LOG_NG << "resuming from loaded linger state...\n"; - //as carryover information is stored in the snapshot, we have to re-store it after loading a linger state - saved_game_.set_snapshot(config()); - if(!is_observer()) { - persist_.end_transaction(); - } - return VICTORY; - } - } //end if + } //end block } catch(const game::load_game_exception &) { // Loading a new game is effectively a quit. // @@ -546,6 +541,7 @@ possible_end_play_signal playsingle_controller::play_turn() } //If the loop exits due to the last team having been processed, //player_number_ will be 1 too high + //TODO: Why else could the loop exit? if(player_number_ > static_cast(gamestate_.board_.teams().size())) player_number_ = gamestate_.board_.teams().size(); @@ -633,9 +629,9 @@ possible_end_play_signal playsingle_controller::play_side() // Give control to a human for this turn. player_type_changed_ = true; temporary_human = true; - } catch (end_level_exception & e) { + } catch (end_level_exception &e) { return possible_end_play_signal(e.to_struct()); - } catch (restart_turn_exception & e) { + } catch (restart_turn_exception&) { player_type_changed_ = true; } if(!player_type_changed_) @@ -700,19 +696,19 @@ void playsingle_controller::before_human_turn() } ai::manager::raise_turn_started(); - if(do_autosaves_ && level_result_ == NONE) { + if(do_autosaves_ && !is_regular_game_end()) { update_savegame_snapshot(); savegame::autosave_savegame save(saved_game_, *gui_, preferences::save_compression_format()); save.autosave(game_config::disable_autosave, preferences::autosavemax(), preferences::INFINITE_AUTO_SAVES); } - if(preferences::turn_bell() && level_result_ == NONE) { + if(preferences::turn_bell() && !is_regular_game_end()) { sound::play_bell(game_config::sounds::turn_bell); } } void playsingle_controller::show_turn_dialog(){ - if(preferences::turn_dialog() && (level_result_ == NONE) ) { + if(preferences::turn_dialog() && !is_regular_game_end() ) { blindfold b(*gui_, true); //apply a blindfold for the duration of this dialog gui_->redraw_everything(); gui_->recalculate_minimap(); @@ -896,12 +892,11 @@ possible_end_play_signal playsingle_controller::check_time_over(){ } HANDLE_END_PLAY_SIGNAL( check_victory() ); - - get_end_level_data().proceed_to_next_level = false; - - end_level_struct els = {DEFEAT}; - return possible_end_play_signal (els); - //throw end_level_exception(DEFEAT); + end_level_data e; + e.proceed_to_next_level = false; + e.is_victory = false; + set_end_level_data(e); + return possible_end_play_signal (); } return boost::none; } @@ -921,7 +916,7 @@ void playsingle_controller::force_end_turn(){ void playsingle_controller::check_end_level() { - if (level_result_ == NONE || linger_) + if (!is_regular_game_end() || linger_) { const team &t = gamestate_.board_.teams()[gui_->viewing_team()]; if (!is_browsing() && t.objectives_changed()) { @@ -930,7 +925,7 @@ void playsingle_controller::check_end_level() } return; } - throw end_level_exception(level_result_); + throw end_level_exception(); } @@ -941,9 +936,8 @@ bool playsingle_controller::is_host() const void playsingle_controller::maybe_linger() { - //Make sure [end_level_data] gets writen into the snapshot even when skipping linger mode. - linger_ = true; - if (get_end_level_data_const().transient.linger_mode) { + // mouse_handler expects at least one team for linger mode to work. + if (get_end_level_data_const().transient.linger_mode && !gamestate_.board_.teams().empty()) { linger(); } } diff --git a/src/playsingle_controller.hpp b/src/playsingle_controller.hpp index f6b05c8d7a8b..a2f6ac7d434c 100644 --- a/src/playsingle_controller.hpp +++ b/src/playsingle_controller.hpp @@ -49,13 +49,11 @@ class playsingle_controller : public play_controller LEVEL_RESULT play_scenario(const config::const_child_itors &story, bool skip_replay); - boost::optional play_scenario_init(end_level_data & eld); - LEVEL_RESULT play_scenario_main_loop(end_level_data & eld); + void play_scenario_init(); + void play_scenario_main_loop(); virtual void handle_generic_event(const std::string& name); - virtual void force_end_level(LEVEL_RESULT res) - { level_result_ = res; } virtual void check_end_level(); void report_victory(std::ostringstream &report, team& t, int finishing_bonus_per_turn, int turns_left, int finishing_bonus); @@ -68,7 +66,7 @@ class playsingle_controller : public play_controller class hotkey_handler; virtual bool is_end_turn() const { return end_turn_; } - + std::string describe_result() const; protected: possible_end_play_signal play_turn(); virtual possible_end_play_signal play_side(); @@ -98,7 +96,6 @@ class playsingle_controller : public play_controller bool replaying_; bool skip_next_turn_; bool do_autosaves_; - LEVEL_RESULT level_result_; void linger(); }; diff --git a/src/playturn.cpp b/src/playturn.cpp index 4f153598e5c5..ee5d631f0753 100644 --- a/src/playturn.cpp +++ b/src/playturn.cpp @@ -329,7 +329,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg //The user pressed "end game". Don't throw a network error here or he will get //thrown back to the title screen. do_save(); - throw end_level_exception(QUIT); + throw_quit_game_exception(); default: if (action > 3) { diff --git a/src/replay_controller.cpp b/src/replay_controller.cpp index 6dc24237c5fb..b0e967b1477f 100644 --- a/src/replay_controller.cpp +++ b/src/replay_controller.cpp @@ -43,6 +43,7 @@ #include #include #include +#include static lg::log_domain log_engine("engine"); #define DBG_NG LOG_STREAM(debug, log_engine) @@ -67,8 +68,9 @@ LEVEL_RESULT play_replay_level(const config& game_config, const tdata_cache & td try { rc.reset(new replay_controller(state_of_game.get_replay_starting_pos(), state_of_game, ticks, game_config, tdata, video)); - } catch (end_level_exception & e){ - return e.result; + } catch (end_level_exception&){ + //TODO: When can this happen? + return QUIT; } DBG_NG << "created objects... " << (SDL_GetTicks() - rc->get_ticks()) << std::endl; @@ -442,7 +444,7 @@ void replay_controller::process_oos(const std::string& msg) const message << "\n\n" << _("Error details:") << "\n\n" << msg; if (non_interactive()) { - throw game::game_error(message.str()); //throw end_level_exception(DEFEAT); + throw game::game_error(message.str()); } else { update_savegame_snapshot(); savegame::oos_savegame save(saved_game_, *gui_); @@ -493,7 +495,7 @@ possible_end_play_signal replay_controller::play_replay(){ case END_TURN: return signal; case END_LEVEL: - if ( boost::apply_visitor(get_result(), *signal) == QUIT) { + if(boost::get(*signal).is_quit) { return signal; } } @@ -573,8 +575,8 @@ possible_end_play_signal replay_controller::play_move_or_side(bool one_move) { return boost::none; } } catch(end_level_exception& e){ - //VICTORY/DEFEAT end_level_exception shall not return to title screen - if (e.result != VICTORY && e.result != DEFEAT) { + //dont return to title screen if the game ended the normal way + if (!is_regular_game_end()) { return possible_end_play_signal(e.to_struct()); } } diff --git a/src/replay_controller.hpp b/src/replay_controller.hpp index 541921e52dac..a0d3b50a0b03 100644 --- a/src/replay_controller.hpp +++ b/src/replay_controller.hpp @@ -46,7 +46,6 @@ class replay_controller : public play_controller void replay_skip_animation(); virtual void force_end_turn() {} - virtual void force_end_level(LEVEL_RESULT) {} virtual void check_end_level() {} virtual void on_not_observer() {} diff --git a/src/savegame.cpp b/src/savegame.cpp index 77331b3ba24e..ae81a35bc5ee 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -405,7 +405,7 @@ bool savegame::save_game_interactive(CVideo& video, const std::string& message, const int res = show_save_dialog(video, message, dialog_type); if (res == 2) { - throw end_level_exception(QUIT); //Quit game + throw_quit_game_exception(); //Quit game } if (res == gui2::twindow::OK && check_overwrite(video)) { diff --git a/src/scripting/game_lua_kernel.cpp b/src/scripting/game_lua_kernel.cpp index 8c79a0893ffd..4f55fb6c580f 100644 --- a/src/scripting/game_lua_kernel.cpp +++ b/src/scripting/game_lua_kernel.cpp @@ -1401,7 +1401,7 @@ static int intf_debug(lua_State* L) int game_lua_kernel::intf_check_end_level_disabled(lua_State * L) { - lua_pushboolean(L, play_controller_.get_end_level_data().transient.disabled); + lua_pushboolean(L, play_controller_.is_regular_game_end()); return 1; } @@ -1441,13 +1441,12 @@ int game_lua_kernel::intf_end_level(lua_State *L) { vconfig cfg(luaW_checkvconfig(L, 1)); - end_level_data &data = play_controller_.get_end_level_data(); - - if (data.transient.disabled) { + + if (play_controller_.is_regular_game_end()) { return 0; } - data.transient.disabled = true; - + end_level_data data; + // TODO: is this still needed? // Remove 0-hp units from the unit map to avoid the following problem: // In case a die event triggers an endlevel the dead unit is still as a @@ -1548,12 +1547,8 @@ int game_lua_kernel::intf_end_level(lua_State *L) data.transient.linger_mode = cfg["linger_mode"].to_bool(true) && !teams().empty(); data.transient.reveal_map = cfg["reveal_map"].to_bool(true); - if(!local_human_victory) { - play_controller_.force_end_level(DEFEAT); - } else { - play_controller_.force_end_level(VICTORY); - } - + data.is_victory = local_human_victory; + play_controller_.set_end_level_data(data); return 0; } From f8b4741b8d617e721a5db13709df041f226bbb81 Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sun, 1 Mar 2015 00:22:01 +0100 Subject: [PATCH 09/10] remove unneeded send_data call --- src/playmp_controller.cpp | 6 ------ src/playmp_controller.hpp | 1 - src/playsingle_controller.hpp | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/playmp_controller.cpp b/src/playmp_controller.cpp index f44e075cb16f..9bf75d804aac 100644 --- a/src/playmp_controller.cpp +++ b/src/playmp_controller.cpp @@ -93,12 +93,6 @@ possible_end_play_signal playmp_controller::play_side() return playsingle_controller::play_side(); } -void playmp_controller::before_human_turn(){ - LOG_NG << "playmp::before_human_turn...\n"; - playsingle_controller::before_human_turn(); - turn_data_.send_data(); -} - void playmp_controller::on_not_observer() { remove_blindfold(); } diff --git a/src/playmp_controller.hpp b/src/playmp_controller.hpp index c101087ec737..d4f6d13225c5 100644 --- a/src/playmp_controller.hpp +++ b/src/playmp_controller.hpp @@ -47,7 +47,6 @@ class playmp_controller : public playsingle_controller, public syncmp_handler void stop_network(); virtual possible_end_play_signal play_side(); - virtual void before_human_turn(); virtual possible_end_play_signal play_human_turn(); virtual void after_human_turn(); virtual void finish_side_turn(); diff --git a/src/playsingle_controller.hpp b/src/playsingle_controller.hpp index a2f6ac7d434c..ce707e78eba3 100644 --- a/src/playsingle_controller.hpp +++ b/src/playsingle_controller.hpp @@ -70,7 +70,7 @@ class playsingle_controller : public play_controller protected: possible_end_play_signal play_turn(); virtual possible_end_play_signal play_side(); - virtual void before_human_turn(); + void before_human_turn(); void show_turn_dialog(); void execute_gotos(); virtual possible_end_play_signal play_human_turn(); From b60809e623d89d3667009330cf50e9b96e0aeb0d Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sun, 1 Mar 2015 03:10:18 +0100 Subject: [PATCH 10/10] don't throw exceptions on :droid, :idle This not only simplifies the playsingle/mp_controller code, it also fixes a bug that actions were aborted when issueing a :droid during an ai turn causing corrupt replays or worse. We still use restart_turn_exception to escape insode the ai code but not during the action. Instead we now check player_type_changed_ after every action, just like we do for ai_end_turn_exception and catch it inside ai::manager::play_turn so that the playsingle/mp_controller code doesn't have to handle this exception anymore. --- src/ai/actions.cpp | 7 +++ src/ai/manager.cpp | 2 + src/game_end_exceptions.hpp | 31 +---------- src/menu_events.cpp | 13 ++--- src/mouse_events.cpp | 16 ++---- src/playmp_controller.cpp | 19 +++---- src/playsingle_controller.cpp | 95 ++++++++++++--------------------- src/playsingle_controller.hpp | 3 ++ src/replay_controller.cpp | 21 ++------ src/whiteboard/move.cpp | 1 + src/whiteboard/side_actions.cpp | 1 + 11 files changed, 71 insertions(+), 138 deletions(-) diff --git a/src/ai/actions.cpp b/src/ai/actions.cpp index 0fe8b597b670..bf4045bbc3b8 100644 --- a/src/ai/actions.cpp +++ b/src/ai/actions.cpp @@ -44,6 +44,7 @@ #include "../mouse_handler_base.hpp" #include "../pathfind/teleport.hpp" #include "../play_controller.hpp" +#include "../playsingle_controller.hpp" #include "../recall_list_manager.hpp" #include "../replay_helper.hpp" #include "../resources.hpp" @@ -104,6 +105,12 @@ void action_result::execute() if(resources::controller->is_end_turn()) { throw ai_end_turn_exception(); } + + if(playsingle_controller* psc = dynamic_cast(resources::controller)) { + if(psc->get_player_type_changed()) { + throw restart_turn_exception(); + } + } is_execution_ = false; } diff --git a/src/ai/manager.cpp b/src/ai/manager.cpp index 1010c69c37e5..77336605909f 100644 --- a/src/ai/manager.cpp +++ b/src/ai/manager.cpp @@ -798,6 +798,8 @@ void manager::play_turn( side_number side ){ } catch (const ai_end_turn_exception&) { } + catch(const restart_turn_exception&) { + } const int turn_end_time= SDL_GetTicks(); DBG_AI_MANAGER << "side " << side << ": number of user interactions: "< #include #include -#include MAKE_ENUM(LEVEL_RESULT, (VICTORY, "victory") @@ -59,13 +58,6 @@ class ai_end_turn_exception IMPLEMENT_LUA_JAILBREAK_EXCEPTION(ai_end_turn_exception) }; - -/** - * Struct used to transmit info caught from an end_turn_exception. - */ -struct restart_turn_struct { -}; - /** * Exception used to signal the end of a player turn. */ @@ -80,10 +72,6 @@ class restart_turn_exception { } const char * what() const throw() { return "restart_turn_exception"; } - restart_turn_struct to_struct() - { - return restart_turn_struct(); - } private: @@ -129,7 +117,7 @@ class end_level_exception /** * The two end_*_exceptions are caught and transformed to this signaling object */ -typedef boost::optional > possible_end_play_signal; +typedef boost::optional possible_end_play_signal; #define HANDLE_END_PLAY_SIGNAL( X )\ do\ @@ -138,8 +126,6 @@ do\ X;\ } catch (end_level_exception & e) {\ return possible_end_play_signal(e.to_struct());\ - } catch (restart_turn_exception & e) {\ - return possible_end_play_signal(e.to_struct());\ }\ }\ while(0) @@ -171,21 +157,6 @@ do\ while(0) -enum END_PLAY_SIGNAL_TYPE {END_TURN, END_LEVEL}; - -class get_signal_type : public boost::static_visitor { -public: - END_PLAY_SIGNAL_TYPE operator()(restart_turn_struct &) const - { - return END_TURN; - } - - END_PLAY_SIGNAL_TYPE operator()(end_level_struct& ) const - { - return END_LEVEL; - } -}; - /** * The non-persistent part of end_level_data */ diff --git a/src/menu_events.cpp b/src/menu_events.cpp index a8cb851f32fc..39c4890c9c9e 100644 --- a/src/menu_events.cpp +++ b/src/menu_events.cpp @@ -61,6 +61,7 @@ #include "menu_events.hpp" #include "mouse_events.hpp" #include "play_controller.hpp" +#include "playsingle_controller.hpp" #include "preferences_display.hpp" #include "replay.hpp" #include "replay_helper.hpp" @@ -2711,9 +2712,9 @@ void console_handler::do_droid() { } menu_handler_.teams()[side - 1].toggle_droid(); if(team_num_ == side) { - //if it is our turn at the moment, we have to indicate to the - //play_controller, that we are no longer in control - throw restart_turn_exception(); + if(playsingle_controller* psc = dynamic_cast(&menu_handler_.pc_)) { + psc->set_player_type_changed(); + } } } else if (menu_handler_.teams()[side - 1].is_local_ai()) { // menu_handler_.teams()[side - 1].make_human(); @@ -2756,9 +2757,9 @@ void console_handler::do_idle() { //toggle the proxy controller between idle / non idle menu_handler_.teams()[side - 1].toggle_idle(); if(team_num_ == side) { - //if it is our turn at the moment, we have to indicate to the - //play_controller, that we are no longer in control - throw restart_turn_exception(); + if(playsingle_controller* psc = dynamic_cast(&menu_handler_.pc_)) { + psc->set_player_type_changed(); + } } } menu_handler_.textbox_info_.close(*menu_handler_.gui_); diff --git a/src/mouse_events.cpp b/src/mouse_events.cpp index 28e31be4e45f..9878a3e7cdf5 100644 --- a/src/mouse_events.cpp +++ b/src/mouse_events.cpp @@ -67,7 +67,6 @@ #include "SDL_mouse.h" // for SDL_GetMouseState #include "SDL_video.h" // for SDL_Color -class restart_turn_exception; namespace gui { class slider; } static lg::log_domain log_engine("engine"); @@ -889,18 +888,9 @@ size_t mouse_handler::move_unit_along_route(const std::vector & st } } - size_t moves = 0; - try { - - LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back() << "\n"; - moves = actions::move_unit_and_record(steps, &pc_.get_undo_stack(), - false, true, &interrupted); - } catch(restart_turn_exception&) { - cursor::set(cursor::NORMAL); - gui().invalidate_game_status(); - throw; - } - + LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back() << "\n"; + size_t moves = actions::move_unit_and_record(steps, &pc_.get_undo_stack(), false, true, &interrupted); + cursor::set(cursor::NORMAL); gui().invalidate_game_status(); diff --git a/src/playmp_controller.cpp b/src/playmp_controller.cpp index 9bf75d804aac..fd53c1abe03f 100644 --- a/src/playmp_controller.cpp +++ b/src/playmp_controller.cpp @@ -128,7 +128,7 @@ possible_end_play_signal playmp_controller::play_human_turn() if (!linger_ || is_host()) { end_turn_enable(true); } - while(!end_turn_) { + while(!end_turn_ && !player_type_changed_) { try { config cfg; @@ -137,6 +137,7 @@ possible_end_play_signal playmp_controller::play_human_turn() HANDLE_END_PLAY_SIGNAL( res = turn_data_.process_network_data(cfg) ); if (res == turn_info::PROCESS_RESTART_TURN) { + player_type_changed_ = true; // Clean undo stack if turn has to be restarted (losing control) if ( undo_stack_->can_undo() ) { @@ -155,7 +156,7 @@ possible_end_play_signal playmp_controller::play_human_turn() while( undo_stack_->can_undo() ) undo_stack_->undo(); - return possible_end_play_signal(restart_turn_struct()); + return boost::none; } else if(res == turn_info::PROCESS_END_LINGER) { @@ -199,7 +200,7 @@ possible_end_play_signal playmp_controller::play_idle_loop() remove_blindfold(); - while (!end_turn_) + while (!end_turn_ && !player_type_changed_) { try { @@ -210,7 +211,8 @@ possible_end_play_signal playmp_controller::play_idle_loop() if (res == turn_info::PROCESS_RESTART_TURN) { - return possible_end_play_signal(restart_turn_struct()); + player_type_changed_ = true; + return boost::none; } } @@ -268,7 +270,7 @@ void playmp_controller::linger() gamestate_.board_.set_all_units_user_end_turn(); set_end_scenario_button(); - + assert(is_regular_game_end()); if ( get_end_level_data_const().transient.reveal_map ) { // Change the view of all players and observers // to see the whole map regardless of shroud and fog. @@ -295,12 +297,6 @@ void playmp_controller::linger() LOG_NG << "caught end-level-exception" << std::endl; reset_end_scenario_button(); throw; - } catch (restart_turn_exception&) { - // thrown if the host leaves the game (sends [leave_game]), we need - // to stay in this loop to stay in linger mode, otherwise the game - // gets aborted - LOG_NG << "caught end-turn-exception" << std::endl; - quit = false; } catch (network::error&) { LOG_NG << "caught network-error-exception" << std::endl; quit = false; @@ -478,6 +474,7 @@ void playmp_controller::do_idle_notification() void playmp_controller::maybe_linger() { // mouse_handler expects at least one team for linger mode to work. + assert(is_regular_game_end()); if (!get_end_level_data_const().transient.linger_mode || gamestate_.board_.teams().empty()) { if(!is_host()) { // If we continue without lingering we need to diff --git a/src/playsingle_controller.cpp b/src/playsingle_controller.cpp index 55e25a7b61ac..8a9722dd57e3 100644 --- a/src/playsingle_controller.cpp +++ b/src/playsingle_controller.cpp @@ -56,7 +56,6 @@ #include "hotkey/hotkey_item.hpp" #include -#include static lg::log_domain log_aitesting("aitesting"); #define LOG_AIT LOG_STREAM(info, log_aitesting) @@ -213,9 +212,6 @@ void playsingle_controller::play_scenario_init() { this->reset_end_level_data(); } return; - } catch (restart_turn_exception &) { - assert(false && "caugh end_turn exception in a bad place... terminating."); - std::terminate(); } replaying_ = (recorder.at_end() == false); @@ -247,9 +243,6 @@ void playsingle_controller::play_scenario_init() { this->reset_end_level_data(); } return; - } catch (restart_turn_exception&) { - assert(false && "caugh end_turn exception in a bad place... terminating."); - std::terminate(); } @@ -264,8 +257,6 @@ void playsingle_controller::play_scenario_init() { this->reset_end_level_data(); } return; - } catch (restart_turn_exception &) { - //someone decided to droid a side during start event. Ignore it. } sync.do_final_checkup(); gui_->recalculate_minimap(); @@ -312,16 +303,10 @@ void playsingle_controller::play_scenario_main_loop() { possible_end_play_signal signal = play_turn(); if (signal) { - switch (boost::apply_visitor( get_signal_type(), *signal )) { - case END_LEVEL: - if(boost::get(*signal).is_quit) { - reset_end_level_data(); - } - return; - case END_TURN: - assert(false && "end turn signal propagated to playsingle_controller::play_scenario_main_loop, terminating"); - std::terminate(); + if(signal->is_quit) { + reset_end_level_data(); } + return; } do_autosaves_ = true; @@ -554,7 +539,7 @@ possible_end_play_signal playsingle_controller::play_turn() possible_end_play_signal playsingle_controller::play_idle_loop() { - while(!end_turn_) { + while(!end_turn_ && !player_type_changed_) { HANDLE_END_PLAY_SIGNAL( play_slice() ); gui_->draw(); SDL_Delay(10); @@ -571,8 +556,6 @@ possible_end_play_signal playsingle_controller::play_side() maybe_do_init_side(); } catch (end_level_exception & e) { return possible_end_play_signal(e.to_struct()); - } catch (restart_turn_exception &) { - // ignore it. Since we don't have started the loop below yet, we dont need to restart it. } //flag used when we fallback from ai and give temporarily control to human bool temporary_human = false; @@ -597,23 +580,18 @@ possible_end_play_signal playsingle_controller::play_side() if (!end_turn_) { signal = play_human_turn(); } - - if (signal) { - switch (boost::apply_visitor(get_signal_type(), *signal)) { - case END_LEVEL: - return signal; - case END_TURN: { - player_type_changed_ = true; - // If new controller is not human, - // reset gui to prev human one - if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { - int s = find_human_team_before_current_player(); - if (s <= 0) { - s = gui_->playing_side(); - } - update_gui_to_player(s-1); - } + if (signal) { + return signal; + } + if (player_type_changed_) { + // If new controller is not human, + // reset gui to prev human one + if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { + int s = find_human_team_before_current_player(); + if (s <= 0) { + s = gui_->playing_side(); } + update_gui_to_player(s-1); } } } @@ -631,8 +609,6 @@ possible_end_play_signal playsingle_controller::play_side() temporary_human = true; } catch (end_level_exception &e) { return possible_end_play_signal(e.to_struct()); - } catch (restart_turn_exception&) { - player_type_changed_ = true; } if(!player_type_changed_) { @@ -651,30 +627,27 @@ possible_end_play_signal playsingle_controller::play_side() if (!end_turn_) { signal = play_idle_loop(); } - + if (signal) { - switch (boost::apply_visitor(get_signal_type(), *signal)) { - case END_LEVEL: - return signal; - case END_TURN: { - LOG_NG << "Escaped from idle state with exception!" << std::endl; - player_type_changed_ = true; - // If new controller is not human, - // reset gui to prev human one - if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { - int s = find_human_team_before_current_player(); - if (s <= 0) { - s = gui_->playing_side(); - } - update_gui_to_player(s-1); - } - else { - //This side was previously not human controlled. - update_gui_to_player(player_number_ - 1); - } + return signal; + } + if (player_type_changed_) { + // If new controller is not human, + // reset gui to prev human one + if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { + int s = find_human_team_before_current_player(); + if (s <= 0) { + s = gui_->playing_side(); } + update_gui_to_player(s-1); } + else { + //This side was previously not human controlled. + update_gui_to_player(player_number_ - 1); + } + } + } else { assert(current_team().is_empty()); // Do nothing. @@ -694,6 +667,7 @@ void playsingle_controller::before_human_turn() if(end_turn_) { return; } + //TODO: why do we need the next line? ai::manager::raise_turn_started(); if(do_autosaves_ && !is_regular_game_end()) { @@ -732,7 +706,7 @@ possible_end_play_signal playsingle_controller::play_human_turn() { } end_turn_enable(true); - while(!end_turn_) { + while(!end_turn_ && !player_type_changed_) { HANDLE_END_PLAY_SIGNAL( play_slice() ); gui_->draw(); } @@ -937,6 +911,7 @@ bool playsingle_controller::is_host() const void playsingle_controller::maybe_linger() { // mouse_handler expects at least one team for linger mode to work. + assert(is_regular_game_end()); if (get_end_level_data_const().transient.linger_mode && !gamestate_.board_.teams().empty()) { linger(); } diff --git a/src/playsingle_controller.hpp b/src/playsingle_controller.hpp index ce707e78eba3..9ba135195208 100644 --- a/src/playsingle_controller.hpp +++ b/src/playsingle_controller.hpp @@ -67,6 +67,9 @@ class playsingle_controller : public play_controller class hotkey_handler; virtual bool is_end_turn() const { return end_turn_; } std::string describe_result() const; + + bool get_player_type_changed() const { return player_type_changed_; } + void set_player_type_changed() { player_type_changed_ = true; } protected: possible_end_play_signal play_turn(); virtual possible_end_play_signal play_side(); diff --git a/src/replay_controller.cpp b/src/replay_controller.cpp index b0e967b1477f..cc571b28b25c 100644 --- a/src/replay_controller.cpp +++ b/src/replay_controller.cpp @@ -43,7 +43,6 @@ #include #include #include -#include static lg::log_domain log_engine("engine"); #define DBG_NG LOG_STREAM(debug, log_engine) @@ -78,15 +77,7 @@ LEVEL_RESULT play_replay_level(const config& game_config, const tdata_cache & td possible_end_play_signal signal = play_replay_level_main_loop(*rc, is_unit_test); if (signal) { - switch( boost::apply_visitor( get_signal_type(), *signal ) ) { - case END_LEVEL: - DBG_NG << "play_replay_level: end_level_exception" << std::endl; - break; - case END_TURN: - DBG_NG << "Unchecked end_turn_exception signal propogated to replay controller play_replay_level! Terminating." << std::endl; - assert(false && "unchecked end turn exception in replay controller"); - throw 42; - } + DBG_NG << "play_replay_level: end_level_exception" << std::endl; } return VICTORY; @@ -491,15 +482,9 @@ possible_end_play_signal replay_controller::play_replay(){ possible_end_play_signal signal = play_replay_main_loop(); if(signal) { - switch ( boost::apply_visitor(get_signal_type(), *signal)) { - case END_TURN: - return signal; - case END_LEVEL: - if(boost::get(*signal).is_quit) { - return signal; - } + if(signal->is_quit) { + return signal; } - } if (!is_playing_) { diff --git a/src/whiteboard/move.cpp b/src/whiteboard/move.cpp index 4a658593d6a7..cf5ad6e42171 100644 --- a/src/whiteboard/move.cpp +++ b/src/whiteboard/move.cpp @@ -226,6 +226,7 @@ void move::execute(bool& success, bool& complete) events::mouse_handler& mouse_handler = resources::controller->get_mouse_handler_base(); num_steps = mouse_handler.move_unit_along_route(steps, interrupted); } catch (restart_turn_exception&) { + //This exception is thown by the ai code, i dont know whther the whiteboard can interact with the ai so i leave this catch here for now set_arrow_brightness(ARROW_BRIGHTNESS_STANDARD); throw; // we rely on the caller to delete this action } diff --git a/src/whiteboard/side_actions.cpp b/src/whiteboard/side_actions.cpp index 974dbfbc0cdb..7a4c87fa50af 100644 --- a/src/whiteboard/side_actions.cpp +++ b/src/whiteboard/side_actions.cpp @@ -337,6 +337,7 @@ bool side_actions::execute(side_actions::iterator position) try { action->execute(action_successful, action_complete); } catch (restart_turn_exception&) { + //This exception is thown by the ai code, i dont know whther the whiteboard can interact with the ai so i leave this catch here for now synced_erase(position); LOG_WB << "End turn exception caught during execution, deleting action. " << *this << "\n"; //validate actions at next map rebuild