1 /*
2    Copyright (C) 2006 - 2018 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
3    wesnoth playlevel Copyright (C) 2003 by David White <dave@whitevine.net>
4    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY.
12 
13    See the COPYING file for more details.
14 */
15 
16 /**
17  *  @file
18  *  Logic for single-player game.
19  */
20 
21 #include "playsingle_controller.hpp"
22 
23 #include "actions/undo.hpp"
24 #include "ai/manager.hpp"
25 #include "ai/testing.hpp"
26 #include "display_chat_manager.hpp"
27 #include "game_end_exceptions.hpp"
28 #include "game_events/pump.hpp"
29 #include "preferences/game.hpp"
30 #include "gettext.hpp"
31 #include "gui/dialogs/story_viewer.hpp"
32 #include "gui/dialogs/transient_message.hpp"
33 #include "hotkey/hotkey_handler_sp.hpp"
34 #include "log.hpp"
35 #include "map/label.hpp"
36 #include "map/map.hpp"
37 #include "playturn.hpp"
38 #include "random_deterministic.hpp"
39 #include "replay_helper.hpp"
40 #include "resources.hpp"
41 #include "savegame.hpp"
42 #include "sound.hpp"
43 #include "synced_context.hpp"
44 #include "formula/string_utils.hpp"
45 #include "events.hpp"
46 #include "save_blocker.hpp"
47 #include "scripting/plugins/context.hpp"
48 #include "soundsource.hpp"
49 #include "statistics.hpp"
50 #include "units/unit.hpp"
51 #include "wesnothd_connection_error.hpp"
52 #include "whiteboard/manager.hpp"
53 #include "hotkey/hotkey_item.hpp"
54 #include <boost/dynamic_bitset.hpp>
55 
56 static lg::log_domain log_aitesting("aitesting");
57 #define LOG_AIT LOG_STREAM(info, log_aitesting)
58 //If necessary, this define can be replaced with `#define LOG_AIT std::cout` to restore previous behavior
59 
60 static lg::log_domain log_engine("engine");
61 #define ERR_NG LOG_STREAM(err, log_engine)
62 #define LOG_NG LOG_STREAM(info, log_engine)
63 
64 static lg::log_domain log_enginerefac("enginerefac");
65 #define LOG_RG LOG_STREAM(info, log_enginerefac)
66 
playsingle_controller(const config & level,saved_game & state_of_game,const ter_data_cache & tdata,bool skip_replay)67 playsingle_controller::playsingle_controller(const config& level,
68 	saved_game& state_of_game, const ter_data_cache & tdata, bool skip_replay)
69 	: play_controller(level, state_of_game, tdata, skip_replay)
70 	, cursor_setter_(cursor::NORMAL)
71 	, textbox_info_()
72 	, replay_sender_(*resources::recorder)
73 	, network_reader_([this](config& cfg) {return receive_from_wesnothd(cfg);})
74 	, turn_data_(replay_sender_, network_reader_)
75 	, end_turn_(END_TURN_NONE)
76 	, skip_next_turn_(false)
77 	, ai_fallback_(false)
78 	, replay_controller_()
79 {
80 	hotkey_handler_.reset(new hotkey_handler(*this, saved_game_)); //upgrade hotkey handler to the sp (whiteboard enabled) version
81 
82 
83 	// game may need to start in linger mode
84 	linger_ = this->is_regular_game_end();
85 
86 	plugins_context_->set_accessor_string("level_result", std::bind(&playsingle_controller::describe_result, this));
87 	plugins_context_->set_accessor_int("turn", std::bind(&play_controller::turn, this));
88 }
89 
describe_result() const90 std::string playsingle_controller::describe_result() const
91 {
92 	if(!is_regular_game_end()) {
93 		return "NONE";
94 	}
95 	else if(get_end_level_data_const().is_victory){
96 		return "VICTORY";
97 	}
98 	else {
99 		return "DEFEAT";
100 	}
101 }
102 
init_gui()103 void playsingle_controller::init_gui()
104 {
105 	LOG_NG << "Initializing GUI... " << (SDL_GetTicks() - ticks()) << "\n";
106 	play_controller::init_gui();
107 
108 	// Scroll to the starting position of the first team. If there is a
109 	// human team, use that team; otherwise use team 1. If the map defines
110 	// a starting position for the selected team, scroll to that tile. Note
111 	// this often does not matter since many scenario start with messages,
112 	// which will usually scroll to the speaker. Also note that the map
113 	// does not necessarily define the starting positions. While usually
114 	// best to use the map, the scenarion may explicitly set the positions,
115 	// overriding those found in the map (if any).
116 	if(map_start_.valid())
117 	{
118 		gui_->scroll_to_tile(map_start_, game_display::WARP, false);
119 		LOG_NG << "Found good stored ui location " << map_start_ << "\n";
120 	}
121 	else
122 	{
123 		int scroll_team = gamestate().first_human_team_ + 1;
124 		if (scroll_team == 0) {
125 			scroll_team = 1;
126 		}
127 		map_location loc(gamestate().board_.map().starting_position(scroll_team));
128 		if ((loc.x >= 0) && (loc.y >= 0)) {
129 			gui_->scroll_to_tile(loc, game_display::WARP);
130 			LOG_NG << "Found bad stored ui location " << map_start_ << " using side starting location " << loc << "\n";
131 		}
132 		else {
133 			LOG_NG << "Found bad stored ui location\n";
134 		}
135 	}
136 
137 	update_locker lock_display(gui_->video(), is_skipping_replay());
138 	get_hotkey_command_executor()->set_button_state();
139 }
140 
141 
play_scenario_init()142 void playsingle_controller::play_scenario_init()
143 {
144 	// At the beginning of the scenario, save a snapshot as replay_start
145 	if(saved_game_.replay_start().empty()){
146 		saved_game_.replay_start() = to_config();
147 	}
148 	start_game();
149 	if(!saved_game_.classification().random_mode.empty() && is_networked_mp()) {
150 		// This won't cause errors later but we should notify the user about it in case he didn't knew it.
151 		gui2::show_transient_message(
152 			// TODO: find a better title
153 			_("Game Error"),
154 			_("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.")
155 		);
156 	}
157 	return;
158 }
159 
play_scenario_main_loop()160 void playsingle_controller::play_scenario_main_loop()
161 {
162 	LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks()) << "\n";
163 
164 
165 	// Avoid autosaving after loading, but still
166 	// allow the first turn to have an autosave.
167 	ai_testing::log_game_start();
168 	if(gamestate().board_.teams().empty())
169 	{
170 		ERR_NG << "Playing game with 0 teams." << std::endl;
171 	}
172 	while(true) {
173 		try {
174 			play_turn();
175 			if (is_regular_game_end()) {
176 				turn_data_.send_data();
177 				return;
178 			}
179 		}
180 		catch(const reset_gamestate_exception& ex) {
181 			//
182 			// TODO:
183 			//
184 			// The MP replay feature still doesn't work properly (causes OOS)
185 			// because:
186 			//
187 			// 1) The undo stack is not reset along with the gamestate (fixed).
188 			// 2) The server_request_number_ is not reset along with the
189 			//    gamestate (fixed).
190 			// 3) chat and other unsynced actions are inserted in the middle of
191 			//    the replay bringing the replay_pos in unorder (fixed).
192 			// 4) untracked changes in side controllers are lost when resetting
193 			//    gamestate (fixed).
194 			// 5) The game should have a stricter check for whether the loaded
195 			//    game is actually a parent of this game.
196 			// 6) If an action was undone after a game was saved it can cause
197 			//    OOS if the undone action is in the snapshot of the saved
198 			//    game (luckily this is never the case for autosaves).
199 			//
200 			boost::dynamic_bitset<> local_players;
201 			local_players.resize(gamestate().board_.teams().size(), true);
202 			//Preserve side controllers, because we won't get the side controoller updates again when replaying.
203 			for(size_t i = 0; i < local_players.size(); ++i) {
204 				local_players[i] = gamestate().board_.teams()[i].is_local();
205 			}
206 			if(ex.start_replay) {
207 				// MP "Back to turn"
208 				statistics::read_stats(*ex.stats_);
209 			} else {
210 				// SP replay
211 				statistics::reset_current_scenario();
212 			}
213 			reset_gamestate(*ex.level, (*ex.level)["replay_pos"]);
214 			for(size_t i = 0; i < local_players.size(); ++i) {
215 				resources::gameboard->teams()[i].set_local(local_players[i]);
216 			}
217 			play_scenario_init();
218 			if (replay_controller_ == nullptr) {
219 				replay_controller_.reset(new replay_controller(*this, false, ex.level, [this](){ on_replay_end(false); } ));
220 			}
221 			if(ex.start_replay) {
222 				replay_controller_->play_replay();
223 			}
224 		}
225 	} //end for loop
226 }
227 
play_scenario(const config & level)228 LEVEL_RESULT playsingle_controller::play_scenario(const config& level)
229 {
230 	LOG_NG << "in playsingle_controller::play_scenario()...\n";
231 
232 	// Start music.
233 	for(const config &m : level.child_range("music")) {
234 		sound::play_music_config(m, true);
235 	}
236 	sound::commit_music_changes();
237 
238 	if(!this->is_skipping_replay() && !this->is_skipping_story()) {
239 		// Combine all the [story] tags into a single config. Handle this here since
240 		// storyscreen::controller doesn't have a default constructor.
241 		config cfg;
242 		for(const auto& iter : level.child_range("story")) {
243 			cfg.append_children(iter);
244 		}
245 
246 		if(!cfg.empty()) {
247 			gui2::dialogs::story_viewer::display(get_scenario_name(), cfg);
248 		}
249 	}
250 	gui_->labels().read(level);
251 
252 	// Read sound sources
253 	assert(soundsources_manager_ != nullptr);
254 	for (const config &s : level.child_range("sound_source")) {
255 		try {
256 			soundsource::sourcespec spec(s);
257 			soundsources_manager_->add(spec);
258 		} catch (const bad_lexical_cast &) {
259 			ERR_NG << "Error when parsing sound_source config: bad lexical cast." << std::endl;
260 			ERR_NG << "sound_source config was: " << s.debug() << std::endl;
261 			ERR_NG << "Skipping this sound source..." << std::endl;
262 		}
263 	}
264 	LOG_NG << "entering try... " << (SDL_GetTicks() - ticks()) << "\n";
265 	try {
266 		play_scenario_init();
267 		// clears level config;
268 		this->saved_game_.remove_snapshot();
269 
270 		if (!is_regular_game_end() && !linger_) {
271 			play_scenario_main_loop();
272 		}
273 		if (game_config::exit_at_end) {
274 			exit(0);
275 		}
276 		const bool is_victory = get_end_level_data_const().is_victory;
277 
278 		if(gamestate().gamedata_.phase() <= game_data::PRESTART) {
279 			gui_->video().clear_screen();
280 		}
281 
282 		ai_testing::log_game_end();
283 
284 		const end_level_data& end_level = get_end_level_data_const();
285 
286 		if (gamestate().board_.teams().empty())
287 		{
288 			//store persistent teams
289 			saved_game_.set_snapshot(config());
290 
291 			return LEVEL_RESULT::VICTORY; // this is probably only a story scenario, i.e. has its endlevel in the prestart event
292 		}
293 		if(linger_) {
294 			LOG_NG << "resuming from loaded linger state...\n";
295 			//as carryover information is stored in the snapshot, we have to re-store it after loading a linger state
296 			saved_game_.set_snapshot(config());
297 			if(!is_observer()) {
298 				persist_.end_transaction();
299 			}
300 			return LEVEL_RESULT::VICTORY;
301 		}
302 		pump().fire(is_victory ? "local_victory" : "local_defeat");
303 		{ // Block for set_scontext_synced_base
304 			set_scontext_synced_base sync;
305 			pump().fire(end_level.proceed_to_next_level ? "victory" : "defeat");
306 			pump().fire("scenario_end");
307 		}
308 		if(end_level.proceed_to_next_level) {
309 			gamestate().board_.heal_all_survivors();
310 		}
311 		if(is_observer()) {
312 			gui2::show_transient_message(_("Game Over"), _("The game is over."));
313 			return LEVEL_RESULT::OBSERVER_END;
314 		}
315 		// If we're a player, and the result is victory/defeat, then send
316 		// a message to notify the server of the reason for the game ending.
317 		send_to_wesnothd(config {
318 			"info", config {
319 				"type", "termination",
320 				"condition", "game over",
321 				"result", is_victory ? "victory" : "defeat",
322 			},
323 		});
324 		// Play victory music once all victory events
325 		// are finished, if we aren't observers and the
326 		// carryover dialog isn't disabled.
327 		//
328 		// Some scenario authors may use 'continue'
329 		// result for something that is not story-wise
330 		// a victory, so let them use [music] tags
331 		// instead should they want special music.
332 		const std::string& end_music = select_music(is_victory);
333 		if((!is_victory || end_level.transient.carryover_report) && !end_music.empty()) {
334 			sound::empty_playlist();
335 			sound::play_music_once(end_music);
336 		}
337 		persist_.end_transaction();
338 		return is_victory ? LEVEL_RESULT::VICTORY : LEVEL_RESULT::DEFEAT;
339 	} catch(const savegame::load_game_exception &) {
340 		// Loading a new game is effectively a quit.
341 		saved_game_.clear();
342 		throw;
343 	} catch(const wesnothd_error& e) {
344 		scoped_savegame_snapshot snapshot(*this);
345 		savegame::ingame_savegame save(saved_game_, preferences::save_compression_format());
346 		if(e.message == "") {
347 			save.save_game_interactive(_("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"), savegame::savegame::YES_NO);
348 		} else {
349 			save.save_game_interactive(_("This game has been ended.\nReason: ")+e.message+_("\nDo you want to save the game?"), savegame::savegame::YES_NO);
350 		}
351 		if(dynamic_cast<const ingame_wesnothd_error*>(&e)) {
352 			return LEVEL_RESULT::QUIT;
353 		} else {
354 			throw;
355 		}
356 	}
357 }
358 
play_idle_loop()359 void playsingle_controller::play_idle_loop()
360 {
361 	while(!should_return_to_play_side()) {
362 		play_slice_catch();
363 		SDL_Delay(10);
364 	}
365 }
366 
play_side_impl()367 void playsingle_controller::play_side_impl()
368 {
369 	if (!skip_next_turn_) {
370 		end_turn_ = END_TURN_NONE;
371 	}
372 	if(replay_controller_.get() != nullptr) {
373 		init_side_done_now_ = false;
374 		REPLAY_RETURN res = replay_controller_->play_side_impl();
375 		if(res == REPLAY_FOUND_END_TURN) {
376 			end_turn_ = END_TURN_SYNCED;
377 		}
378 		if (player_type_changed_) {
379 			replay_controller_.reset();
380 		}
381 	} else if((current_team().is_local_human() && current_team().is_proxy_human())) {
382 		LOG_NG << "is human...\n";
383 		// If a side is dead end the turn, but play at least side=1's
384 		// turn in case all sides are dead
385 		if (gamestate().board_.side_units(current_side()) == 0 && !(gamestate().board_.units().empty() && current_side() == 1)) {
386 			end_turn_ = END_TURN_REQUIRED;
387 		}
388 
389 		before_human_turn();
390 		if (end_turn_ == END_TURN_NONE) {
391 			play_human_turn();
392 		}
393 		if ( !player_type_changed_ && !is_regular_game_end()) {
394 			after_human_turn();
395 		}
396 		LOG_NG << "human finished turn...\n";
397 
398 	} else if(current_team().is_local_ai() || (current_team().is_local_human() && current_team().is_droid())) {
399 		play_ai_turn();
400 	} else if(current_team().is_network()) {
401 		play_network_turn();
402 	} else if(current_team().is_local_human() && current_team().is_idle()) {
403 		end_turn_enable(false);
404 		do_idle_notification();
405 		before_human_turn();
406 		if (end_turn_ == END_TURN_NONE) {
407 			play_idle_loop();
408 		}
409 	}
410 	else {
411 		// we should have skipped over empty controllers before so this shouldn't be possible
412 		ERR_NG << "Found invalid side controller " << current_team().controller().to_string() << " (" << current_team().proxy_controller().to_string() << ") for side " << current_team().side() << "\n";
413 	}
414 }
415 
before_human_turn()416 void playsingle_controller::before_human_turn()
417 {
418 	log_scope("player turn");
419 	assert(!linger_);
420 	if(end_turn_ != END_TURN_NONE || is_regular_game_end()) {
421 		return;
422 	}
423 
424 	if(init_side_done_now_ && !game_config::disable_autosave && preferences::autosavemax() > 0) {
425 		scoped_savegame_snapshot snapshot(*this);
426 		savegame::autosave_savegame save(saved_game_, preferences::save_compression_format());
427 		save.autosave(game_config::disable_autosave, preferences::autosavemax(), preferences::INFINITE_AUTO_SAVES);
428 	}
429 
430 	if(preferences::turn_bell()) {
431 		sound::play_bell(game_config::sounds::turn_bell);
432 	}
433 }
434 
show_turn_dialog()435 void playsingle_controller::show_turn_dialog(){
436 	if(preferences::turn_dialog() && !is_regular_game_end() ) {
437 		blindfold b(*gui_, true); //apply a blindfold for the duration of this dialog
438 		gui_->redraw_everything();
439 		gui_->recalculate_minimap();
440 		std::string message = _("It is now $name|’s turn");
441 		utils::string_map symbols;
442 		symbols["name"] = gamestate().board_.get_team(current_side()).side_name();
443 		message = utils::interpolate_variables_into_string(message, &symbols);
444 		gui2::show_transient_message("", message);
445 	}
446 }
447 
execute_gotos()448 void playsingle_controller::execute_gotos()
449 {
450 	if(should_return_to_play_side())
451 	{
452 		return;
453 	}
454 	try
455 	{
456 		menu_handler_.execute_gotos(mouse_handler_, current_side());
457 	}
458 	catch (const return_to_play_side_exception&)
459 	{
460 	}
461 }
462 
play_human_turn()463 void playsingle_controller::play_human_turn() {
464 	show_turn_dialog();
465 
466 	if (!preferences::disable_auto_moves()) {
467 		execute_gotos();
468 	}
469 
470 	end_turn_enable(true);
471 	while(!should_return_to_play_side()) {
472 		check_objectives();
473 		play_slice_catch();
474 	}
475 
476 }
477 
linger()478 void playsingle_controller::linger()
479 {
480 	LOG_NG << "beginning end-of-scenario linger\n";
481 	linger_ = true;
482 
483 	// If we need to set the status depending on the completion state
484 	// the key to it is here.
485 	gui_->set_game_mode(game_display::LINGER);
486 
487 	// change the end-turn button text to its alternate label
488 	gui_->get_theme().refresh_title2("button-endturn", "title2");
489 	gui_->invalidate_theme();
490 	gui_->redraw_everything();
491 
492 	// End all unit moves
493 	gamestate().board_.set_all_units_user_end_turn();
494 	try {
495 		// Same logic as single-player human turn, but
496 		// *not* the same as multiplayer human turn.
497 		end_turn_enable(true);
498 		end_turn_ = END_TURN_NONE;
499 		while(end_turn_ == END_TURN_NONE) {
500 			play_slice();
501 		}
502 	} catch(const savegame::load_game_exception &) {
503 		// Loading a new game is effectively a quit.
504 		saved_game_.clear();
505 		throw;
506 	}
507 
508 	// revert the end-turn button text to its normal label
509 	gui_->get_theme().refresh_title2("button-endturn", "title");
510 	gui_->invalidate_theme();
511 	gui_->redraw_everything();
512 	gui_->set_game_mode(game_display::RUNNING);
513 
514 	LOG_NG << "ending end-of-scenario linger\n";
515 }
516 
end_turn_enable(bool enable)517 void playsingle_controller::end_turn_enable(bool enable)
518 {
519 	gui_->enable_menu("endturn", enable);
520 	get_hotkey_command_executor()->set_button_state();
521 }
522 
523 
after_human_turn()524 void playsingle_controller::after_human_turn()
525 {
526 	// Clear moves from the GUI.
527 	gui_->set_route(nullptr);
528 	gui_->unhighlight_reach();
529 }
530 
play_ai_turn()531 void playsingle_controller::play_ai_turn()
532 {
533 	LOG_NG << "is ai...\n";
534 
535 	end_turn_enable(false);
536 	gui_->recalculate_minimap();
537 
538 	const cursor::setter cursor_setter(cursor::WAIT);
539 
540 	// Correct an oddball case where a human could have left delayed shroud
541 	// updates on before giving control to the AI. (The AI does not bother
542 	// with the undo stack, so it cannot delay shroud updates.)
543 	team & cur_team = current_team();
544 	if ( !cur_team.auto_shroud_updates() ) {
545 		// We just took control, so the undo stack is empty. We still need
546 		// to record this change for the replay though.
547 		synced_context::run_and_store("auto_shroud", replay_helper::get_auto_shroud(true));
548 	}
549 	undo_stack().clear();
550 
551 	turn_data_.send_data();
552 	try {
553 		try {
554 			if (!should_return_to_play_side()) {
555 				ai::manager::get_singleton().play_turn(current_side());
556 			}
557 		}
558 		catch (const return_to_play_side_exception&) {
559 		}
560 		catch (const fallback_ai_to_human_exception&) {
561 			current_team().make_human();
562 			player_type_changed_ = true;
563 			ai_fallback_ = true;
564 		}
565 	}
566 	catch(...) {
567 		turn_data_.sync_network();
568 		throw;
569 	}
570 	if(!should_return_to_play_side()) {
571 		end_turn_ = END_TURN_REQUIRED;
572 	}
573 	turn_data_.sync_network();
574 	gui_->recalculate_minimap();
575 	gui_->invalidate_unit();
576 	gui_->invalidate_game_status();
577 	gui_->invalidate_all();
578 }
579 
580 
581 /**
582  * Will handle sending a networked notification in descendent classes.
583  */
do_idle_notification()584 void playsingle_controller::do_idle_notification()
585 {
586 	gui_->get_chat_manager().add_chat_message(time(nullptr), "Wesnoth", 0,
587 		"This side is in an idle state. To proceed with the game, the host must assign it to another controller.",
588 		events::chat_handler::MESSAGE_PUBLIC, false);
589 }
590 
591 /**
592  * Will handle networked turns in descendent classes.
593  */
play_network_turn()594 void playsingle_controller::play_network_turn()
595 {
596 	// There should be no networked sides in single-player.
597 	ERR_NG << "Networked team encountered by playsingle_controller." << std::endl;
598 }
599 
600 
handle_generic_event(const std::string & name)601 void playsingle_controller::handle_generic_event(const std::string& name){
602 	if (name == "ai_user_interact"){
603 		play_slice(false);
604 	}
605 }
606 
607 
608 
end_turn()609 void playsingle_controller::end_turn(){
610 	if (linger_)
611 		end_turn_ = END_TURN_REQUIRED;
612 	else if (!is_browsing() && menu_handler_.end_turn(current_side())){
613 		end_turn_ = END_TURN_REQUIRED;
614 	}
615 }
616 
force_end_turn()617 void playsingle_controller::force_end_turn(){
618 	skip_next_turn_ = true;
619 	end_turn_ = END_TURN_REQUIRED;
620 }
621 
check_objectives()622 void playsingle_controller::check_objectives()
623 {
624 	if (!gamestate().board_.teams().empty()) {
625 		const team &t = gamestate().board_.teams()[gui_->viewing_team()];
626 
627 		if (!is_regular_game_end() && !is_browsing() && t.objectives_changed()) {
628 			show_objectives();
629 		}
630 	}
631 }
632 
633 
maybe_linger()634 void playsingle_controller::maybe_linger()
635 {
636 	// mouse_handler expects at least one team for linger mode to work.
637 	assert(is_regular_game_end());
638 	if (get_end_level_data_const().transient.linger_mode && !gamestate().board_.teams().empty()) {
639 		linger();
640 	}
641 }
642 
sync_end_turn()643 void playsingle_controller::sync_end_turn()
644 {
645 	//We cannot add [end_turn] to the recorder while executing another action.
646 	assert(synced_context::synced_state() == synced_context::UNSYNCED);
647 	if(end_turn_ == END_TURN_REQUIRED && current_team().is_local())
648 	{
649 		//TODO: we should also send this immediately.
650 		resources::recorder->end_turn(gamestate_->next_player_number_);
651 		end_turn_ = END_TURN_SYNCED;
652 	}
653 
654 	assert(end_turn_ == END_TURN_SYNCED);
655 	skip_next_turn_ = false;
656 
657 	if(ai_fallback_) {
658 		current_team().make_ai();
659 		ai_fallback_ = false;
660 	}
661 }
662 
update_viewing_player()663 void playsingle_controller::update_viewing_player()
664 {
665 	if(replay_controller_ && replay_controller_->is_controlling_view()) {
666 		replay_controller_->update_viewing_player();
667 	}
668 	//Update viewing team in case it has changed during the loop.
669 	else if(int side_num = play_controller::find_last_visible_team()) {
670 		if(side_num != this->gui_->viewing_side()) {
671 			update_gui_to_player(side_num - 1);
672 		}
673 	}
674 }
675 
reset_replay()676 void playsingle_controller::reset_replay()
677 {
678 	if(replay_controller_ && replay_controller_->allow_reset_replay()) {
679 		replay_controller_->stop_replay();
680 		throw reset_gamestate_exception(replay_controller_->get_reset_state(), {}, false);
681 	}
682 	else {
683 		ERR_NG << "received invalid reset replay\n";
684 	}
685 }
686 
enable_replay(bool is_unit_test)687 void playsingle_controller::enable_replay(bool is_unit_test)
688 {
689 	replay_controller_.reset(new replay_controller(*this, gamestate().has_human_sides(), std::shared_ptr<config>( new config(saved_game_.get_replay_starting_point())), std::bind(&playsingle_controller::on_replay_end, this, is_unit_test)));
690 	if(is_unit_test) {
691 		replay_controller_->play_replay();
692 	}
693 }
694 
should_return_to_play_side() const695 bool playsingle_controller::should_return_to_play_side() const
696 {
697 	if(player_type_changed_ || is_regular_game_end()) {
698 		return true;
699 	}
700 	else if (end_turn_ == END_TURN_NONE || replay_controller_.get() != 0 || current_team().is_network()) {
701 		return false;
702 	}
703 	else {
704 		return true;
705 	}
706 }
707 
on_replay_end(bool is_unit_test)708 void playsingle_controller::on_replay_end(bool is_unit_test)
709 {
710 	if(is_networked_mp()) {
711 		set_player_type_changed();
712 	}
713 	else if(is_unit_test) {
714 		replay_controller_->return_to_play_side();
715 		if(!is_regular_game_end()) {
716 			end_level_data e;
717 			e.proceed_to_next_level = false;
718 			e.is_victory = false;
719 			set_end_level_data(e);
720 		}
721 	}
722 }
723