void playsingle_controller::play_scenario_main_loop() { LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks()) << "\n"; // Avoid autosaving after loading, but still // allow the first turn to have an autosave. ai_testing::log_game_start(); if(gamestate().board_.teams().empty()) { ERR_NG << "Playing game with 0 teams." << std::endl; } while(true) { try { play_turn(); if (is_regular_game_end()) { turn_data_.send_data(); return; } gamestate_->player_number_ = 1; } catch(const reset_gamestate_exception& ex) { // // TODO: // // The MP replay feature still doesn't work properly (causes OOS) // because: // // 1) The undo stack is not reset along with the gamestate (fixed). // 2) The server_request_number_ is not reset along with the // gamestate (fixed). // 3) chat and other unsynced actions are inserted in the middle of // the replay bringing the replay_pos in unorder (fixed). // 4) untracked changes in side controllers are lost when resetting // gamestate (fixed). // 5) The game should have a stricter check for whether the loaded // game is actually a parent of this game. // 6) If an action was undone after a game was saved it can cause // OOS if the undone action is in the snapshot of the saved // game (luckily this is never the case for autosaves). // boost::dynamic_bitset<> local_players; local_players.resize(gamestate().board_.teams().size(), true); //Preserve side controllers, becasue we won't get the side controoller updates again when replaying. for(size_t i = 0; i < local_players.size(); ++i) { local_players[i] = gamestate().board_.teams()[i].is_local(); } reset_gamestate(*ex.level, (*ex.level)["replay_pos"]); for(size_t i = 0; i < local_players.size(); ++i) { resources::gameboard->teams()[i].set_local(local_players[i]); } play_scenario_init(); replay_.reset(new replay_controller(*this, false, ex.level)); if(ex.start_replay) { replay_->play_replay(); } } } //end for loop }
void playsingle_controller::play_scenario_main_loop() { LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks()) << "\n"; // Avoid autosaving after loading, but still // allow the first turn to have an autosave. ai_testing::log_game_start(); if(gamestate().board_.teams().empty()) { ERR_NG << "Playing game with 0 teams." << std::endl; } while(true) { try { play_turn(); if (is_regular_game_end()) { return; } gamestate_->player_number_ = 1; } catch(const reset_gamestate_exception& ex) { /** @TODO: The mp replay feature still doesnt work properly (casues OOS) becasue: 1) The undo stack is not reset along with the gamestate (fixed). 2) The server_request_number_ is not reset along with the gamestate (fixed). 3) chat and other unsynced actions are inserted in the middle of the replay bringing the replay_pos in unorder (fixed). 4) untracked changes in side controllers are lost when resetting gamestate. 5) The game should have a stricter check for whether the loaded game is actually a parent of this game. 6) If an action was undone after a game was saved it can casue if teh undone action is in the snapshot of the saved game. (luckyli this is never the case for autosaves) */ reset_gamestate(*ex.level, (*ex.level)["replay_pos"]); play_scenario_init(*ex.level); mp_replay_.reset(new replay_controller(*this, false, ex.level)); mp_replay_->play_replay(); } } //end for loop }
LEVEL_RESULT playsingle_controller::play_scenario(const config& level) { LOG_NG << "in playsingle_controller::play_scenario()...\n"; // Start music. BOOST_FOREACH(const config &m, level.child_range("music")) { sound::play_music_config(m); } sound::commit_music_changes(); if(!this->is_skipping_replay()) { show_story(gui_->video(), get_scenario_name(), level.child_range("story")); } gui_->labels().read(level); // Read sound sources assert(soundsources_manager_ != NULL); BOOST_FOREACH(const config &s, level.child_range("sound_source")) { try { soundsource::sourcespec spec(s); soundsources_manager_->add(spec); } catch (bad_lexical_cast &) { ERR_NG << "Error when parsing sound_source config: bad lexical cast." << std::endl; ERR_NG << "sound_source config was: " << s.debug() << std::endl; ERR_NG << "Skipping this sound source..." << std::endl; } } LOG_NG << "entering try... " << (SDL_GetTicks() - ticks()) << "\n"; try { play_scenario_init(); // clears level config; this->saved_game_.remove_snapshot(); if (!is_regular_game_end() && !linger_) { play_scenario_main_loop(); } if (game_config::exit_at_end) { exit(0); } const bool is_victory = get_end_level_data_const().is_victory; if(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() ); update_rect(0, 0, gui_->video().getx(), gui_->video().gety()); } 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 (!is_victory) { set_defeat_music_list(end_level.transient.custom_endlevel_music); } else { set_victory_music_list(end_level.transient.custom_endlevel_music); } } if (gamestate().board_.teams().empty()) { //store persistent teams saved_game_.set_snapshot(config()); return LEVEL_RESULT::VICTORY; // this is probably only a story scenario, i.e. has its endlevel in the prestart event } 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 LEVEL_RESULT::VICTORY; } pump().fire(is_victory ? "victory" : "defeat"); { // Block for set_scontext_synced_base set_scontext_synced_base sync; pump().fire("scenario end"); } if(end_level.proceed_to_next_level) { gamestate().board_.heal_all_survivors(); } if(is_observer()) { gui2::show_transient_message(gui_->video(), _("Game Over"), _("The game is over.")); return LEVEL_RESULT::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. network::send_data(config_of ("info", config_of ("type", "termination") ("condition", "game over") ("result", is_victory ? "victory" : "defeat") )); // Play victory music once all victory events // are finished, if we aren't observers. // // Some scenario authors may use 'continue' // result for something that is not story-wise // a victory, so let them use [music] tags // instead should they want special music. const std::string& end_music = is_victory ? select_victory_music() : select_defeat_music(); if(end_music.empty() != true) { sound::play_music_once(end_music); } persist_.end_transaction(); return is_victory ? LEVEL_RESULT::VICTORY : LEVEL_RESULT::DEFEAT; } catch(const game::load_game_exception &) { // Loading a new game is effectively a quit. // if ( game::load_game_exception::game != "" ) { saved_game_ = saved_game(); } throw; } catch(network::error& e) { bool disconnect = false; if(e.socket) { e.disconnect(); disconnect = true; } scoped_savegame_snapshot snapshot(*this); savegame::ingame_savegame save(saved_game_, *gui_, preferences::save_compression_format()); save.save_game_interactive(gui_->video(), _("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"), gui::YES_NO); if(disconnect) { throw network::error(); } else { return LEVEL_RESULT::QUIT; } } return LEVEL_RESULT::QUIT; }
LEVEL_RESULT playsingle_controller::play_scenario( const config::const_child_itors &story, bool skip_replay) { LOG_NG << "in playsingle_controller::play_scenario()...\n"; // Start music. BOOST_FOREACH(const config &m, level_.child_range("music")) { sound::play_music_config(m); } sound::commit_music_changes(); if(!skip_replay) { show_story(*gui_, level_["name"], story); } gui_->labels().read(level_); // Read sound sources assert(soundsources_manager_ != NULL); BOOST_FOREACH(const config &s, level_.child_range("sound_source")) { try { soundsource::sourcespec spec(s); soundsources_manager_->add(spec); } catch (bad_lexical_cast &) { ERR_NG << "Error when parsing sound_source config: bad lexical cast." << std::endl; ERR_NG << "sound_source config was: " << s.debug() << std::endl; ERR_NG << "Skipping this sound source..." << std::endl; } } 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(); end_level.carryover_percentage = level_["carryover_percentage"].to_int(game_config::gold_carryover_percentage); end_level.carryover_add = level_["carryover_add"].to_bool(); bool past_prestart = false; LOG_NG << "entering try... " << (SDL_GetTicks() - ticks_) << "\n"; try { possible_end_play_signal signal = play_scenario_init(end_level, past_prestart); if (!signal) { signal = play_scenario_main_loop(end_level, past_prestart); } if (signal) { switch (boost::apply_visitor( get_signal_type(), *signal )) { //BEGIN CASES case END_TURN: assert(false && "end turn signal propogated to playsingle_controller::play_scenario. This results in terminate!"); throw 42; case END_LEVEL: if(!past_prestart) { sdl::draw_solid_tinted_rectangle( 0, 0, gui_->video().getx(), gui_->video().gety(), 0, 0, 0, 1.0, gui_->video().getSurface() ); update_rect(0, 0, gui_->video().getx(), gui_->video().gety()); } ai_testing::log_game_end(); LEVEL_RESULT end_level_result = boost::apply_visitor( get_result(), *signal ); if (!end_level.transient.custom_endlevel_music.empty()) { if (end_level_result == DEFEAT) { set_defeat_music_list(end_level.transient.custom_endlevel_music); } else { set_victory_music_list(end_level.transient.custom_endlevel_music); } } if (gamestate_.board_.teams().empty()) { //store persistent teams saved_game_.set_snapshot(config()); 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 (end_level_result == QUIT) { return QUIT; } else if (end_level_result == DEFEAT) { saved_game_.classification().completion = "defeat"; game_events::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; } } else if (end_level_result == VICTORY) { saved_game_.classification().completion = !end_level.transient.linger_mode ? "running" : "victory"; game_events::fire("victory"); // // Play victory music once all victory events // are finished, if we aren't observers. // // Some scenario authors may use 'continue' // result for something that is not story-wise // a victory, so let them use [music] tags // instead should they want special music. // if (!obs && end_level.transient.linger_mode) { const std::string& victory_music = select_victory_music(); if(victory_music.empty() != true) sound::play_music_once(victory_music); } // Add all the units that survived the scenario. // this function doesn't move unit to the recalllist anymore i just keep this name to prevent merging conflicts. LOG_NG << "Add units that survived the scenario to the recall list.\n"; gamestate_.board_.all_survivors_to_recall(); saved_game_.remove_snapshot(); if(!is_observer()) { 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; } break; //END CASES } // END SWITCH } //end if } catch(const game::load_game_exception &) { // Loading a new game is effectively a quit. // if ( game::load_game_exception::game != "" ) { saved_game_ = saved_game(); } throw; } catch(network::error& e) { bool disconnect = false; if(e.socket) { e.disconnect(); disconnect = true; } savegame::ingame_savegame save(saved_game_, *gui_, to_config(), preferences::save_compression_format()); save.save_game_interactive(gui_->video(), _("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"), gui::YES_NO); if(disconnect) { throw network::error(); } else { return QUIT; } } return QUIT; }