void play_controller::play_side() { //check for team-specific items in the scenario gui_->parse_team_overlays(); do { update_viewing_player(); { save_blocker blocker; maybe_do_init_side(); if(is_regular_game_end()) { return; } } // This flag can be set by derived classes (in overridden functions). player_type_changed_ = false; statistics::reset_turn_stats(gamestate().board_.teams()[current_side() - 1].save_id()); play_side_impl(); if(is_regular_game_end()) { return; } } while (player_type_changed_); // Keep looping if the type of a team (human/ai/networked) // has changed mid-turn sync_end_turn(); }
void play_controller::check_victory() { if(linger_) { return; } if (is_regular_game_end()) { return; } bool continue_level, found_player, found_network_player, invalidate_all; std::set<unsigned> not_defeated; gamestate().board_.check_victory(continue_level, found_player, found_network_player, invalidate_all, not_defeated, remove_from_carryover_on_defeat_); if (invalidate_all) { gui_->invalidate_all(); } if (continue_level) { return ; } if (found_player || found_network_player) { pump().fire("enemies_defeated"); if (is_regular_game_end()) { return; } } DBG_EE << "victory_when_enemies_defeated: " << victory_when_enemies_defeated_ << std::endl; DBG_EE << "found_player: " << found_player << std::endl; DBG_EE << "found_network_player: " << found_network_player << std::endl; if (!victory_when_enemies_defeated_ && (found_player || found_network_player)) { // This level has asked not to be ended by this condition. return; } if (gui_->video().non_interactive()) { LOG_AIT << "winner: "; for (unsigned l : not_defeated) { std::string ai = ai::manager::get_active_ai_identifier_for_side(l); if (ai.empty()) ai = "default ai"; LOG_AIT << l << " (using " << ai << ") "; } LOG_AIT << std::endl; ai_testing::log_victory(not_defeated); } DBG_EE << "throwing end level exception..." << std::endl; //Also proceed to the next scenario when another player survived. 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); }
void playsingle_controller::play_turn() { whiteboard_manager_->on_gamestate_change(); gui_->new_turn(); gui_->invalidate_game_status(); events::raise_draw_event(); LOG_NG << "turn: " << turn() << "\n"; if(non_interactive()) { LOG_AIT << "Turn " << turn() << ":" << std::endl; } for (; player_number_ <= int(gamestate_.board_.teams().size()); ++player_number_) { // If a side is empty skip over it. if (current_team().is_empty()) continue; { save_blocker blocker; init_side_begin(false); if(init_side_done_) { //This is the case in a reloaded game where teh side was initilizes before saving the game. init_side_end(); } } ai_testing::log_turn_start(player_number_); play_side(); if(is_regular_game_end()) { return; } finish_side_turn(); if(is_regular_game_end()) { return; } if(non_interactive()) { LOG_AIT << " Player " << player_number_ << ": " << current_team().villages().size() << " Villages" << std::endl; ai_testing::log_turn_end(player_number_); } } //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<int>(gamestate_.board_.teams().size())) player_number_ = gamestate_.board_.teams().size(); finish_turn(); // Time has run out check_time_over(); }
void play_controller::play_turn() { whiteboard_manager_->on_gamestate_change(); gui_->new_turn(); gui_->invalidate_game_status(); events::raise_draw_event(); LOG_NG << "turn: " << turn() << "\n"; if(gui_->video().non_interactive()) { LOG_AIT << "Turn " << turn() << ":" << std::endl; } for (; gamestate_->player_number_ <= int(gamestate().board_.teams().size()); ++gamestate_->player_number_) { // If a side is empty skip over it. if (current_team().is_empty()) { continue; } init_side_begin(); if(gamestate_->init_side_done()) { // This is the case in a reloaded game where the side was initialized before saving the game. init_side_end(); } ai_testing::log_turn_start(current_side()); play_side(); if(is_regular_game_end()) { return; } finish_side_turn(); if(is_regular_game_end()) { return; } if(gui_->video().non_interactive()) { LOG_AIT << " Player " << current_side() << ": " << current_team().villages().size() << " Villages" << std::endl; ai_testing::log_turn_end(current_side()); } } // If the loop exits due to the last team having been processed. gamestate_->player_number_ = gamestate().board_.teams().size(); finish_turn(); // Time has run out check_time_over(); }
void playmp_controller::play_network_turn(){ LOG_NG << "is networked...\n"; end_turn_enable(false); turn_data_.send_data(); while(end_turn_ != END_TURN_SYNCED && !is_regular_game_end() && !player_type_changed_) { if (!network_processing_stopped_) { process_network_data(); if (!mp_info_ || mp_info_->skip_replay_until_turn > 0) { skip_replay_ = false; } } play_slice_catch(); if (!network_processing_stopped_){ turn_data_.send_data(); } gui_->draw(); } LOG_NG << "finished networked...\n"; }
void playmp_controller::process_network_data(bool chat_only) { if(end_turn_ == END_TURN_SYNCED || is_regular_game_end() || player_type_changed_) { return; } turn_info::PROCESS_DATA_RESULT res = turn_info::PROCESS_CONTINUE; config cfg; if(!resources::recorder->at_end()) { res = turn_info::replay_to_process_data_result(do_replay()); } else if(network_reader_.read(cfg)) { res = turn_data_.process_network_data(cfg, chat_only); } if (res == turn_info::PROCESS_CANNOT_HANDLE) { network_reader_.push_front(std::move(cfg)); } else if (res == turn_info::PROCESS_RESTART_TURN) { player_type_changed_ = true; } else if (res == turn_info::PROCESS_END_TURN) { end_turn_ = END_TURN_SYNCED; } else if (res == turn_info::PROCESS_END_LEVEL) { } else if (res == turn_info::PROCESS_END_LINGER) { replay::process_error("Received unexpected next_scenario during the game"); } }
void playsingle_controller::check_time_over(){ bool time_left = gamestate_.tod_manager_.next_turn(gamestate_.gamedata_); it_is_a_new_turn_ = true; if(!time_left) { LOG_NG << "firing time over event...\n"; set_scontext_synced_base sync; pump().fire("time over"); LOG_NG << "done firing time over event...\n"; //if turns are added while handling 'time over' event if (gamestate_.tod_manager_.is_time_left()) { return; } if(non_interactive()) { LOG_AIT << "time over (draw)\n"; ai_testing::log_draw(); } check_victory(); if (is_regular_game_end()) { return; } end_level_data e; e.proceed_to_next_level = false; e.is_victory = false; set_end_level_data(e); } }
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::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(); } }
void play_controller::start_game() { fire_preload(); if(!gamestate().start_event_fired_) { gamestate().start_event_fired_ = true; resources::recorder->add_start_if_not_there_yet(); resources::recorder->get_next_action(); set_scontext_synced sync; fire_prestart(); if (is_regular_game_end()) { return; } for ( int side = gamestate().board_.teams().size(); side != 0; --side ) actions::clear_shroud(side, false, false); init_gui(); LOG_NG << "first_time..." << (is_skipping_replay() ? "skipping" : "no skip") << "\n"; events::raise_draw_event(); fire_start(); if (is_regular_game_end()) { return; } sync.do_final_checkup(); gui_->recalculate_minimap(); // Initialize countdown clock. for (const team& t : gamestate().board_.teams()) { if (saved_game_.mp_settings().mp_countdown) { t.set_countdown_time(1000 * saved_game_.mp_settings().mp_countdown_init_time); } } } else { init_gui(); events::raise_draw_event(); gamestate().gamedata_.set_phase(game_data::PLAY); gui_->recalculate_minimap(); } }
void playsingle_controller::check_objectives() { const team &t = gamestate().board_.teams()[gui_->viewing_team()]; if (!is_regular_game_end() && !is_browsing() && t.objectives_changed()) { dialogs::show_objectives(get_scenario_name().str(), t.objectives()); t.reset_objectives_changed(); } }
void playsingle_controller::check_objectives() { if (!gamestate().board_.teams().empty()) { const team &t = gamestate().board_.teams()[gui_->viewing_team()]; if (!is_regular_game_end() && !is_browsing() && t.objectives_changed()) { show_objectives(); } } }
void playsingle_controller::before_human_turn() { log_scope("player turn"); assert(!linger_); if(end_turn_ != END_TURN_NONE) { return; } //TODO: why do we need the next line? ai::manager::raise_turn_started(); if(init_side_done_now_ && !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() && !is_regular_game_end()) { sound::play_bell(game_config::sounds::turn_bell); } }
void playsingle_controller::on_replay_end(bool is_unit_test) { if(is_unit_test) { replay_->return_to_play_side(); if(!is_regular_game_end()) { end_level_data e; e.proceed_to_next_level = false; e.is_victory = false; set_end_level_data(e); } } }
bool playsingle_controller::should_return_to_play_side() { if(player_type_changed_ || is_regular_game_end()) { return true; } else if (end_turn_ == END_TURN_NONE || replay_.get() != 0 || current_team().is_network()) { return false; } else { return true; } }
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"; } }
void playsingle_controller::show_turn_dialog(){ 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(); std::string message = _("It is now $name|’s turn"); utils::string_map symbols; symbols["name"] = gamestate().board_.teams()[current_side() - 1].side_name(); message = utils::interpolate_variables_into_string(message, &symbols); gui2::show_transient_message(gui_->video(), "", message); } }
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 // make sure the host uploads the next scenario // before we attempt to download it. wait_for_upload(); } } else { linger(); } }
void playsingle_controller::play_side_impl() { if (!skip_next_turn_) { end_turn_ = END_TURN_NONE; } if(replay_.get() != NULL) { REPLAY_RETURN res = replay_->play_side_impl(); if(res == REPLAY_FOUND_END_TURN) { end_turn_ = END_TURN_SYNCED; } if (player_type_changed_) { replay_.reset(); } } else if((current_team().is_local_human() && current_team().is_proxy_human())) { LOG_NG << "is human...\n"; // If a side is dead end the turn, but play at least side=1's // turn in case all sides are dead if (gamestate().board_.side_units(current_side()) == 0 && !(gamestate().board_.units().size() == 0 && current_side() == 1)) { end_turn_ = END_TURN_REQUIRED; } before_human_turn(); if (end_turn_ == END_TURN_NONE) { play_human_turn(); } if ( !player_type_changed_ && !is_regular_game_end()) { after_human_turn(); } LOG_NG << "human finished turn...\n"; } else if(current_team().is_local_ai() || (current_team().is_local_human() && current_team().is_droid())) { play_ai_turn(); } else if(current_team().is_network()) { play_network_turn(); } else if(current_team().is_local_human() && current_team().is_idle()) { end_turn_enable(false); do_idle_notification(); before_human_turn(); if (end_turn_ == END_TURN_NONE) { play_idle_loop(); } } else { // we should have skipped over empty controllers before so this shouldn't be possible ERR_NG << "Found invalid side controller " << current_team().controller().to_string() << " (" << current_team().proxy_controller().to_string() << ") for side " << current_team().side() << "\n"; } }
void playsingle_controller::before_human_turn() { log_scope("player turn"); assert(!linger_); if(end_turn_ != END_TURN_NONE || is_regular_game_end()) { return; } if(init_side_done_now_) { scoped_savegame_snapshot snapshot(*this); 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()) { sound::play_bell(game_config::sounds::turn_bell); } }
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) { play_turn(); if (is_regular_game_end()) { return; } player_number_ = 1; } //end for loop }
void playmp_controller::linger() { LOG_NG << "beginning end-of-scenario linger\n"; linger_ = true; // If we need to set the status depending on the completion state // we're needed here. gui_->set_game_mode(game_display::LINGER); // End all unit moves 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. update_gui_to_player(gui_->viewing_team(), true); } bool quit; do { quit = true; try { // reimplement parts of play_side() turn_data_.send_data(); end_turn_ = END_TURN_NONE; play_linger_turn(); after_human_turn(); LOG_NG << "finished human turn" << std::endl; } catch (const savegame::load_game_exception&) { LOG_NG << "caught load-game-exception" << std::endl; // this should not happen, the option to load a game is disabled throw; } catch (const ingame_wesnothd_error&) { LOG_NG << "caught network-error-exception" << std::endl; quit = false; } } while (!quit); reset_end_scenario_button(); LOG_NG << "ending end-of-scenario linger\n"; }
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 }
virtual bool should_return_to_play_side() { return is_regular_game_end(); }
virtual bool should_return_to_play_side() { return player_type_changed_ || end_turn_ != END_TURN_NONE || is_regular_game_end(); }
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; }
void playsingle_controller::play_side() { //check for team-specific items in the scenario gui_->parse_team_overlays(); maybe_do_init_side(); if(is_regular_game_end()) { return; } //flag used when we fallback from ai and give temporarily control to human bool temporary_human = false; do { //Update viewing team in case it has changed during the loop. if(int side_num = play_controller::find_last_visible_team()) { if(side_num != this->gui_->viewing_side()) { update_gui_to_player(side_num - 1); } } // This flag can be set by derived classes (in overridden functions). player_type_changed_ = false; if (!skip_next_turn_) end_turn_ = END_TURN_NONE; statistics::reset_turn_stats(gamestate_.board_.teams()[player_number_ - 1].save_id()); if((current_team().is_local_human() && current_team().is_proxy_human()) || temporary_human) { LOG_NG << "is human...\n"; temporary_human = false; // If a side is dead end the turn, but play at least side=1's // turn in case all sides are dead if (gamestate_.board_.side_units(player_number_) == 0 && !(gamestate_.board_.units().size() == 0 && player_number_ == 1)) { end_turn_ = END_TURN_REQUIRED; } before_human_turn(); if (end_turn_ == END_TURN_NONE) { play_human_turn(); } if(is_regular_game_end()) { return; } if ( !player_type_changed_ ) { after_human_turn(); } LOG_NG << "human finished turn...\n"; } else if(current_team().is_local_ai() || (current_team().is_local_human() && current_team().is_droid())) { try { play_ai_turn(); } catch(fallback_ai_to_human_exception&) { // Give control to a human for this turn. player_type_changed_ = true; temporary_human = true; } if(is_regular_game_end()) { return; } } else if(current_team().is_network()) { play_network_turn(); if(is_regular_game_end()) { return; } } else if(current_team().is_local_human() && current_team().is_idle()) { end_turn_enable(false); do_idle_notification(); before_human_turn(); if (end_turn_ == END_TURN_NONE) { play_idle_loop(); if(is_regular_game_end()) { return; } } } else { assert(current_team().is_empty()); // Do nothing. } } while (player_type_changed_); // Keep looping if the type of a team (human/ai/networked) // has changed mid-turn sync_end_turn(); assert(end_turn_ == END_TURN_SYNCED); skip_next_turn_ = false; }