mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-26 03:08:37 +00:00
refactor playsingle_controller game loop.
The main intention is to move the linger mode into the loop so that the replay can play until linger mode is reached. In particular to allow replays to show victory events aswell.
This commit is contained in:
parent
e3d1e2b4f5
commit
2845e864f4
@ -85,12 +85,6 @@ level_result::type campaign_controller::playsingle_scenario(end_level_data &end_
|
||||
}
|
||||
|
||||
end_level = playcontroller.get_end_level_data();
|
||||
carryover_show_gold(playcontroller.gamestate(), playcontroller.is_observer(), is_unit_test_);
|
||||
|
||||
if(!video::headless()) {
|
||||
playcontroller.maybe_linger();
|
||||
}
|
||||
|
||||
state_.set_snapshot(playcontroller.to_config());
|
||||
return res;
|
||||
}
|
||||
@ -107,13 +101,6 @@ level_result::type campaign_controller::playmp_scenario(end_level_data &end_leve
|
||||
|
||||
end_level = playcontroller.get_end_level_data();
|
||||
|
||||
if(res != level_result::type::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)
|
||||
carryover_show_gold(playcontroller.gamestate(), playcontroller.is_observer(), is_unit_test_);
|
||||
}
|
||||
|
||||
playcontroller.maybe_linger();
|
||||
playcontroller.update_savegame_snapshot();
|
||||
|
||||
if(mp_info_) {
|
||||
@ -162,6 +149,7 @@ level_result::type campaign_controller::play_game()
|
||||
#endif
|
||||
{
|
||||
res = playmp_scenario(end_level);
|
||||
//todo: i removed the code that could return observer_end
|
||||
}
|
||||
} catch(const leavegame_wesnothd_error&) {
|
||||
LOG_NG << "The game was remotely ended";
|
||||
|
@ -584,9 +584,8 @@ config play_controller::to_config() const
|
||||
return cfg;
|
||||
}
|
||||
|
||||
void play_controller::finish_side_turn()
|
||||
void play_controller::finish_side_turn_events()
|
||||
{
|
||||
whiteboard_manager_->on_finish_side_turn(current_side());
|
||||
|
||||
{ // Block for set_scontext_synced
|
||||
set_scontext_synced sync(1);
|
||||
@ -609,8 +608,6 @@ void play_controller::finish_side_turn()
|
||||
sync.do_final_checkup();
|
||||
}
|
||||
mouse_handler_.deselect_hex();
|
||||
gamestate().gamedata_.set_phase(game_data::TURN_STARTING_WAITING);
|
||||
did_autosave_this_turn_ = false;
|
||||
}
|
||||
|
||||
void play_controller::finish_turn()
|
||||
@ -620,7 +617,6 @@ void play_controller::finish_turn()
|
||||
pump().fire("turn_end");
|
||||
pump().fire("turn_" + turn_num + "_end");
|
||||
sync.do_final_checkup();
|
||||
did_tod_sound_this_turn_ = false;
|
||||
}
|
||||
|
||||
bool play_controller::enemies_visible() const
|
||||
@ -1348,78 +1344,6 @@ void play_controller::play_side()
|
||||
sync_end_turn();
|
||||
}
|
||||
|
||||
void play_controller::play_turn()
|
||||
{
|
||||
whiteboard_manager_->on_gamestate_change();
|
||||
gui_->new_turn();
|
||||
gui_->invalidate_game_status();
|
||||
|
||||
LOG_NG << "turn: " << turn();
|
||||
|
||||
if(video::headless()) {
|
||||
LOG_AIT << "Turn " << turn() << ":";
|
||||
}
|
||||
|
||||
int last_player_number = gamestate_->player_number_;
|
||||
int next_player_number = gamestate_->next_player_number_;
|
||||
|
||||
while(gamestate_->player_number_ <= static_cast<int>(get_teams().size())) {
|
||||
gamestate_->next_player_number_ = gamestate_->player_number_ + 1;
|
||||
next_player_number = gamestate_->next_player_number_;
|
||||
last_player_number = gamestate_->player_number_;
|
||||
|
||||
// If a side is empty skip over it.
|
||||
if(!current_team().is_empty()) {
|
||||
init_side_begin();
|
||||
if(is_during_turn()) {
|
||||
// This is the case in a reloaded game where the side was initialized before saving the game (the default case for reloading games).
|
||||
init_side_end();
|
||||
}
|
||||
|
||||
ai_testing::log_turn_start(current_side());
|
||||
play_side();
|
||||
|
||||
// ignore any changes to next_player_number_ that happen after the [end_turn] is sended to the server,
|
||||
// otherwise we will get OOS.
|
||||
next_player_number = gamestate_->next_player_number_;
|
||||
assert(next_player_number <= 2 * static_cast<int>(get_teams().size()));
|
||||
|
||||
if(is_regular_game_end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: play_side() send the [end_turn] to the sever and finish_side_turn() callsie the side turn end
|
||||
// events.
|
||||
// this means that during the side turn end events the clients think it is still the last sides turn
|
||||
// while the server thinks that it is already the next plyers turn. i don'T think this is a problem
|
||||
// though.
|
||||
finish_side_turn();
|
||||
if(is_regular_game_end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(video::headless()) {
|
||||
LOG_AIT << " Player " << current_side() << ": " << current_team().villages().size() << " Villages";
|
||||
ai_testing::log_turn_end(current_side());
|
||||
}
|
||||
}
|
||||
|
||||
gamestate_->player_number_ = next_player_number;
|
||||
}
|
||||
|
||||
// If the loop exits due to the last team having been processed.
|
||||
gamestate_->player_number_ = last_player_number;
|
||||
|
||||
finish_turn();
|
||||
|
||||
// Time has run out
|
||||
check_time_over();
|
||||
|
||||
if(!is_regular_game_end()) {
|
||||
gamestate_->player_number_ = modulo(next_player_number, get_teams().size(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
void play_controller::check_time_over()
|
||||
{
|
||||
const bool time_left = gamestate().tod_manager_.next_turn(&gamestate().gamedata_);
|
||||
|
@ -146,6 +146,8 @@ public:
|
||||
return gamestate().end_level_data_.has_value();
|
||||
}
|
||||
|
||||
bool check_regular_game_end();
|
||||
|
||||
const end_level_data& get_end_level_data() const
|
||||
{
|
||||
return *gamestate().end_level_data_;
|
||||
@ -340,7 +342,7 @@ protected:
|
||||
void fire_start();
|
||||
void start_game();
|
||||
virtual void init_gui();
|
||||
void finish_side_turn();
|
||||
void finish_side_turn_events();
|
||||
void finish_turn(); //this should not throw an end turn or end level exception
|
||||
bool enemies_visible() const;
|
||||
|
||||
@ -433,5 +435,4 @@ protected:
|
||||
virtual void sync_end_turn() {}
|
||||
virtual void check_time_over();
|
||||
virtual void update_viewing_player() = 0;
|
||||
void play_turn();
|
||||
};
|
||||
|
@ -234,7 +234,6 @@ void playmp_controller::reset_end_scenario_button()
|
||||
void playmp_controller::linger()
|
||||
{
|
||||
LOG_NG << "beginning end-of-scenario linger";
|
||||
gamestate().gamedata_.set_phase(game_data::GAME_ENDED);
|
||||
|
||||
// If we need to set the status depending on the completion state
|
||||
// we're needed here.
|
||||
@ -448,6 +447,7 @@ void playmp_controller::maybe_linger()
|
||||
} else {
|
||||
linger();
|
||||
}
|
||||
end_turn_requested_ = true;
|
||||
}
|
||||
|
||||
void playmp_controller::surrender(int side_number)
|
||||
|
@ -26,7 +26,6 @@ class playmp_controller : public playsingle_controller, public syncmp_handler
|
||||
public:
|
||||
playmp_controller(const config& level, saved_game& state_of_game, mp_game_metadata* mp_info);
|
||||
virtual ~playmp_controller();
|
||||
|
||||
void maybe_linger() override;
|
||||
void process_oos(const std::string& err_msg) const override;
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "ai/manager.hpp"
|
||||
#include "ai/testing.hpp"
|
||||
#include "display_chat_manager.hpp"
|
||||
#include "carryover_show_gold.hpp"
|
||||
#include "events.hpp"
|
||||
#include "formula/string_utils.hpp"
|
||||
#include "game_end_exceptions.hpp"
|
||||
@ -143,6 +144,21 @@ void playsingle_controller::init_gui()
|
||||
|
||||
void playsingle_controller::play_scenario_init(const config& level)
|
||||
{
|
||||
gui_->labels().read(level);
|
||||
|
||||
// Read sound sources
|
||||
assert(soundsources_manager_ != nullptr);
|
||||
for(const config& s : level.child_range("sound_source")) {
|
||||
try {
|
||||
soundsource::sourcespec spec(s);
|
||||
soundsources_manager_->add(spec);
|
||||
} catch(const bad_lexical_cast&) {
|
||||
ERR_NG << "Error when parsing sound_source config: bad lexical cast.";
|
||||
ERR_NG << "sound_source config was: " << s.debug();
|
||||
ERR_NG << "Skipping this sound source...";
|
||||
}
|
||||
}
|
||||
|
||||
// At the beginning of the scenario, save a snapshot as replay_start
|
||||
if(saved_game_.replay_start().empty()) {
|
||||
saved_game_.replay_start() = to_config();
|
||||
@ -161,8 +177,96 @@ void playsingle_controller::play_scenario_init(const config& level)
|
||||
_("This multiplayer game uses an alternative random mode, if you don't know what this message means, then "
|
||||
"most likely someone is cheating or someone reloaded a corrupt game."));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
void playsingle_controller::skip_empty_sides(int& side_num)
|
||||
{
|
||||
const int max = side_num + static_cast<int>(get_teams().size());
|
||||
while (gamestate().board_.get_team(modulo(side_num, get_teams().size(), 1)).is_empty()) {
|
||||
if(side_num == max) {
|
||||
throw game::game_error("No teams found");
|
||||
}
|
||||
++side_num;
|
||||
}
|
||||
}
|
||||
|
||||
void playsingle_controller::play_some()
|
||||
{
|
||||
assert(is_regular_game_end() || gamestate().in_phase(game_data::TURN_STARTING_WAITING, game_data::TURN_PLAYING, game_data::TURN_ENDED, game_data::GAME_ENDED));
|
||||
|
||||
if (!is_regular_game_end() && gamestate().in_phase(game_data::TURN_STARTING_WAITING, game_data::TURN_PLAYING)) {
|
||||
if(gamestate().in_phase(game_data::TURN_PLAYING)) {
|
||||
// If we are here we have probably reloaded a savegame
|
||||
init_side_end();
|
||||
}
|
||||
play_side();
|
||||
assert(is_regular_game_end() || gamestate().in_phase(game_data::TURN_ENDED));
|
||||
}
|
||||
|
||||
if (!is_regular_game_end() && gamestate().in_phase(game_data::TURN_ENDED)) {
|
||||
finish_side_turn();
|
||||
}
|
||||
|
||||
if (is_regular_game_end() && !gamestate().in_phase(game_data::GAME_ENDED)) {
|
||||
gamestate().gamedata_.set_phase(game_data::GAME_ENDING);
|
||||
do_end_level();
|
||||
gamestate().gamedata_.set_phase(game_data::GAME_ENDED);
|
||||
}
|
||||
|
||||
if (gamestate().in_phase(game_data::GAME_ENDED)) {
|
||||
if(!get_end_level_data().transient.linger_mode || get_teams().empty() || video::headless()) {
|
||||
end_turn_requested_ = true;
|
||||
}
|
||||
maybe_linger();
|
||||
}
|
||||
}
|
||||
|
||||
void playsingle_controller::finish_side_turn()
|
||||
{
|
||||
if(is_regular_game_end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/// Make a copy, since the [end_turn] was already sent to to server any changes to
|
||||
// next_player_number by wml would cause OOS otherwise.
|
||||
int next_player_number = gamestate_->next_player_number_;
|
||||
whiteboard_manager_->on_finish_side_turn(current_side());
|
||||
|
||||
finish_side_turn_events();
|
||||
if(is_regular_game_end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
skip_empty_sides(next_player_number);
|
||||
bool new_turn = next_player_number > static_cast<int>(get_teams().size());
|
||||
next_player_number = modulo(next_player_number, get_teams().size(), 1);
|
||||
|
||||
if(new_turn) {
|
||||
finish_turn();
|
||||
if(is_regular_game_end()) {
|
||||
return;
|
||||
}
|
||||
// Time has run out
|
||||
check_time_over();
|
||||
if(is_regular_game_end()) {
|
||||
return;
|
||||
}
|
||||
did_tod_sound_this_turn_ = false;
|
||||
}
|
||||
// the turn end event might have deleted sides, so do this again.
|
||||
skip_empty_sides(next_player_number);
|
||||
|
||||
gamestate_->player_number_ = modulo(next_player_number, get_teams().size(), 1);
|
||||
gamestate_->next_player_number_ = gamestate_->player_number_ + 1;
|
||||
|
||||
if(new_turn) {
|
||||
whiteboard_manager_->on_gamestate_change();
|
||||
gui_->new_turn();
|
||||
gui_->invalidate_game_status();
|
||||
}
|
||||
gamestate().gamedata_.set_phase(game_data::TURN_STARTING_WAITING);
|
||||
did_autosave_this_turn_ = false;
|
||||
init_side_begin();
|
||||
}
|
||||
|
||||
void playsingle_controller::play_scenario_main_loop()
|
||||
@ -174,13 +278,9 @@ void playsingle_controller::play_scenario_main_loop()
|
||||
ERR_NG << "Playing game with 0 teams.";
|
||||
}
|
||||
|
||||
while(true) {
|
||||
while(!(gamestate().in_phase(game_data::GAME_ENDED) && end_turn_requested_ )) {
|
||||
try {
|
||||
play_turn();
|
||||
if(is_regular_game_end()) {
|
||||
turn_data_.send_data();
|
||||
return;
|
||||
}
|
||||
play_some();
|
||||
} catch(const reset_gamestate_exception& ex) {
|
||||
//
|
||||
// TODO:
|
||||
@ -235,6 +335,71 @@ void playsingle_controller::play_scenario_main_loop()
|
||||
} // end for loop
|
||||
}
|
||||
|
||||
void playsingle_controller::do_end_level()
|
||||
{
|
||||
if(game_config::exit_at_end) {
|
||||
exit(0);
|
||||
}
|
||||
const bool is_victory = get_end_level_data().is_victory;
|
||||
|
||||
ai_testing::log_game_end();
|
||||
|
||||
const end_level_data& end_level = get_end_level_data();
|
||||
|
||||
if(get_teams().empty()) {
|
||||
// this is probably only a story scenario, i.e. has its endlevel in the prestart event
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
pump().fire(is_victory ? "local_victory" : "local_defeat");
|
||||
|
||||
{ // Block for set_scontext_synced_base
|
||||
set_scontext_synced_base sync;
|
||||
pump().fire(end_level.proceed_to_next_level ? level_result::victory : level_result::defeat);
|
||||
pump().fire("scenario_end");
|
||||
}
|
||||
|
||||
if(end_level.proceed_to_next_level) {
|
||||
gamestate().board_.heal_all_survivors();
|
||||
}
|
||||
|
||||
if(is_observer()) {
|
||||
gui2::show_transient_message(_("Game Over"), _("The game is over."));
|
||||
}
|
||||
|
||||
// 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.
|
||||
send_to_wesnothd(config {
|
||||
"info", config {
|
||||
"type", "termination",
|
||||
"condition", "game over",
|
||||
"result", is_victory ? level_result::victory : level_result::defeat,
|
||||
},
|
||||
});
|
||||
|
||||
// Play victory music once all victory events
|
||||
// are finished, if we aren't observers and the
|
||||
// carryover dialog isn't disabled.
|
||||
//
|
||||
// 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 = select_music(is_victory);
|
||||
if((!is_victory || end_level.transient.carryover_report) && !end_music.empty()) {
|
||||
sound::empty_playlist();
|
||||
sound::play_music_once(end_music);
|
||||
}
|
||||
|
||||
persist_.end_transaction();
|
||||
if(!is_observer()) {
|
||||
//TODO: passing is_observer() when is_observer() is false seems wrong.
|
||||
carryover_show_gold(gamestate(), is_observer(), saved_game_.classification().is_test());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
level_result::type playsingle_controller::play_scenario(const config& level)
|
||||
{
|
||||
LOG_NG << "in playsingle_controller::play_scenario()...";
|
||||
@ -259,101 +424,18 @@ level_result::type playsingle_controller::play_scenario(const config& level)
|
||||
}
|
||||
}
|
||||
|
||||
gui_->labels().read(level);
|
||||
|
||||
// Read sound sources
|
||||
assert(soundsources_manager_ != nullptr);
|
||||
for(const config& s : level.child_range("sound_source")) {
|
||||
try {
|
||||
soundsource::sourcespec spec(s);
|
||||
soundsources_manager_->add(spec);
|
||||
} catch(const bad_lexical_cast&) {
|
||||
ERR_NG << "Error when parsing sound_source config: bad lexical cast.";
|
||||
ERR_NG << "sound_source config was: " << s.debug();
|
||||
ERR_NG << "Skipping this sound source...";
|
||||
}
|
||||
}
|
||||
|
||||
LOG_NG << "entering try... " << (SDL_GetTicks() - ticks());
|
||||
|
||||
try {
|
||||
play_scenario_init(level);
|
||||
// clears level config;
|
||||
saved_game_.remove_snapshot();
|
||||
|
||||
if(!is_regular_game_end() && !is_linger_mode()) {
|
||||
play_scenario_main_loop();
|
||||
}
|
||||
|
||||
if(game_config::exit_at_end) {
|
||||
exit(0);
|
||||
}
|
||||
const bool is_victory = get_end_level_data().is_victory;
|
||||
|
||||
ai_testing::log_game_end();
|
||||
|
||||
const end_level_data& end_level = get_end_level_data();
|
||||
|
||||
if(get_teams().empty()) {
|
||||
|
||||
// this is probably only a story scenario, i.e. has its endlevel in the prestart event
|
||||
return level_result::type::victory;
|
||||
}
|
||||
|
||||
if(is_linger_mode()) {
|
||||
LOG_NG << "resuming from loaded linger state...";
|
||||
if(!is_observer()) {
|
||||
persist_.end_transaction();
|
||||
}
|
||||
|
||||
return level_result::type::victory;
|
||||
}
|
||||
|
||||
gamestate().gamedata_.set_phase(game_data::GAME_ENDING);
|
||||
pump().fire(is_victory ? "local_victory" : "local_defeat");
|
||||
|
||||
{ // Block for set_scontext_synced_base
|
||||
set_scontext_synced_base sync;
|
||||
pump().fire(end_level.proceed_to_next_level ? level_result::victory : level_result::defeat);
|
||||
pump().fire("scenario_end");
|
||||
}
|
||||
|
||||
if(end_level.proceed_to_next_level) {
|
||||
gamestate().board_.heal_all_survivors();
|
||||
}
|
||||
play_scenario_main_loop();
|
||||
|
||||
if(is_observer()) {
|
||||
gui2::show_transient_message(_("Game Over"), _("The game is over."));
|
||||
return level_result::type::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.
|
||||
send_to_wesnothd(config {
|
||||
"info", config {
|
||||
"type", "termination",
|
||||
"condition", "game over",
|
||||
"result", is_victory ? level_result::victory : level_result::defeat,
|
||||
},
|
||||
});
|
||||
|
||||
// Play victory music once all victory events
|
||||
// are finished, if we aren't observers and the
|
||||
// carryover dialog isn't disabled.
|
||||
//
|
||||
// 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 = select_music(is_victory);
|
||||
if((!is_victory || end_level.transient.carryover_report) && !end_music.empty()) {
|
||||
sound::empty_playlist();
|
||||
sound::play_music_once(end_music);
|
||||
}
|
||||
|
||||
persist_.end_transaction();
|
||||
|
||||
return level_result::get_enum(end_level.test_result).value_or(is_victory ? level_result::type::victory : level_result::type::defeat);
|
||||
return level_result::get_enum(get_end_level_data().test_result).value_or(get_end_level_data().is_victory ? level_result::type::victory : level_result::type::defeat);
|
||||
} catch(const savegame::load_game_exception&) {
|
||||
// Loading a new game is effectively a quit.
|
||||
saved_game_.clear();
|
||||
@ -503,7 +585,6 @@ void playsingle_controller::play_human_turn()
|
||||
void playsingle_controller::linger()
|
||||
{
|
||||
LOG_NG << "beginning end-of-scenario linger";
|
||||
gamestate().gamedata_.set_phase(game_data::GAME_ENDED);
|
||||
|
||||
// If we need to set the status depending on the completion state
|
||||
// the key to it is here.
|
||||
@ -520,7 +601,6 @@ void playsingle_controller::linger()
|
||||
// Same logic as single-player human turn, but
|
||||
// *not* the same as multiplayer human turn.
|
||||
end_turn_enable(true);
|
||||
end_turn_requested_ = false;
|
||||
while(!end_turn_requested_) {
|
||||
play_slice();
|
||||
}
|
||||
@ -664,9 +744,8 @@ 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().transient.linger_mode && !get_teams().empty()) {
|
||||
linger();
|
||||
}
|
||||
linger();
|
||||
end_turn_requested_ = true;
|
||||
}
|
||||
|
||||
void playsingle_controller::sync_end_turn()
|
||||
|
@ -42,6 +42,10 @@ public:
|
||||
|
||||
level_result::type play_scenario(const config& level);
|
||||
void play_scenario_init(const config& level);
|
||||
void skip_empty_sides(int& side_num);
|
||||
void play_some();
|
||||
void finish_side_turn();
|
||||
void do_end_level();
|
||||
void play_scenario_main_loop();
|
||||
|
||||
virtual void handle_generic_event(const std::string& name) override;
|
||||
|
Loading…
x
Reference in New Issue
Block a user