wesnoth/src/playsingle_controller.cpp
Chris Beck 2059c6729f use the game config manager's terrain cache in play_controllers
this completes 1a4efb6b22aeb0742c9083a7d0515ebdef799078 by
passing a reference to global terrain data cache to these
functions
2014-11-07 22:34:32 -05:00

1147 lines
34 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Copyright (C) 2006 - 2014 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
wesnoth playlevel Copyright (C) 2003 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* Logic for single-player game.
*/
#include "playsingle_controller.hpp"
#include "actions/undo.hpp"
#include "ai/manager.hpp"
#include "ai/game_info.hpp"
#include "ai/testing.hpp"
#include "dialogs.hpp"
#include "display_chat_manager.hpp"
#include "game_end_exceptions.hpp"
#include "game_events/pump.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "log.hpp"
#include "map_label.hpp"
#include "marked-up_text.hpp"
#include "playturn.hpp"
#include "resources.hpp"
#include "random_new_deterministic.hpp"
#include "replay_helper.hpp"
#include "savegame.hpp"
#include "sound.hpp"
#include "synced_context.hpp"
#include "formula_string_utils.hpp"
#include "events.hpp"
#include "save_blocker.hpp"
#include "soundsource.hpp"
#include "storyscreen/interface.hpp"
#include "unit.hpp"
#include "unit_animation.hpp"
#include "util.hpp"
#include "whiteboard/manager.hpp"
#include "hotkey/hotkey_item.hpp"
#include <boost/foreach.hpp>
static lg::log_domain log_aitesting("aitesting");
#define LOG_AIT LOG_STREAM(info, log_aitesting)
//If necessary, this define can be replaced with `#define LOG_AIT std::cout` to restore previous behavior
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
static lg::log_domain log_enginerefac("enginerefac");
#define LOG_RG LOG_STREAM(info, log_enginerefac)
playsingle_controller::playsingle_controller(const config& level,
saved_game& state_of_game, const int ticks,
const config& game_config, const tdata_cache & tdata,
CVideo& video, bool skip_replay) :
play_controller(level, state_of_game, ticks, game_config, tdata, video, skip_replay),
cursor_setter(cursor::NORMAL),
textbox_info_(),
replay_sender_(recorder),
network_reader_(),
turn_data_(replay_sender_, network_reader_),
end_turn_(false),
player_type_changed_(false),
replaying_(false),
skip_next_turn_(false),
do_autosaves_(false),
level_result_(NONE)
{
// 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";
browse_ = linger_ = true;
}
ai::game_info ai_info;
ai::manager::set_ai_info(ai_info);
ai::manager::add_observer(this) ;
}
playsingle_controller::~playsingle_controller()
{
ai::manager::remove_observer(this) ;
ai::manager::clear_ais() ;
}
void playsingle_controller::init_gui(){
LOG_NG << "Initializing GUI... " << (SDL_GetTicks() - ticks_) << "\n";
play_controller::init_gui();
if(gamestate_.first_human_team_ != -1) {
gui_->scroll_to_tile(gamestate_.board_.map().starting_position(gamestate_.first_human_team_ + 1), game_display::WARP);
}
gui_->scroll_to_tile(gamestate_.board_.map().starting_position(1), game_display::WARP);
update_locker lock_display(gui_->video(),recorder.is_skipping());
set_button_state(*gui_);
events::raise_draw_event();
gui_->draw();
}
void playsingle_controller::recruit(){
if (!browse_)
menu_handler_.recruit(player_number_, mouse_handler_.get_last_hex());
else if (whiteboard_manager_->is_active())
menu_handler_.recruit(resources::screen->viewing_side(), mouse_handler_.get_last_hex());
}
void playsingle_controller::repeat_recruit(){
if (!browse_)
menu_handler_.repeat_recruit(player_number_, mouse_handler_.get_last_hex());
else if (whiteboard_manager_->is_active())
menu_handler_.repeat_recruit(resources::screen->viewing_side(), mouse_handler_.get_last_hex());
}
void playsingle_controller::recall(){
if (!browse_)
menu_handler_.recall(player_number_, mouse_handler_.get_last_hex());
else if (whiteboard_manager_->is_active())
menu_handler_.recall(resources::screen->viewing_side(), mouse_handler_.get_last_hex());
}
void playsingle_controller::toggle_shroud_updates(){
menu_handler_.toggle_shroud_updates(gui_->viewing_team()+1);
}
void playsingle_controller::update_shroud_now(){
menu_handler_.update_shroud_now(gui_->viewing_team()+1);
}
void playsingle_controller::end_turn(){
if (linger_)
end_turn_ = true;
else if (!browse_){
browse_ = true;
end_turn_ = menu_handler_.end_turn(player_number_);
browse_ = end_turn_;
}
}
void playsingle_controller::force_end_turn(){
skip_next_turn_ = true;
end_turn_ = true;
}
void playsingle_controller::check_end_level()
{
if (level_result_ == NONE || linger_)
{
const team &t = gamestate_.board_.teams()[gui_->viewing_team()];
if (!browse_ && t.objectives_changed()) {
dialogs::show_objectives(level_, t.objectives());
t.reset_objectives_changed();
}
return;
}
get_end_level_data().proceed_to_next_level = (level_result_ == VICTORY);
throw end_level_exception(level_result_);
}
void playsingle_controller::rename_unit(){
menu_handler_.rename_unit();
}
void playsingle_controller::create_unit(){
menu_handler_.create_unit(mouse_handler_);
}
void playsingle_controller::change_side(){
menu_handler_.change_side(mouse_handler_);
}
void playsingle_controller::kill_unit(){
menu_handler_.kill_unit(mouse_handler_);
}
void playsingle_controller::label_terrain(bool team_only){
menu_handler_.label_terrain(mouse_handler_, team_only);
}
void playsingle_controller::clear_labels(){
menu_handler_.clear_labels();
}
void playsingle_controller::continue_move(){
menu_handler_.continue_move(mouse_handler_, player_number_);
}
void playsingle_controller::unit_hold_position(){
if (!browse_)
menu_handler_.unit_hold_position(mouse_handler_, player_number_);
}
void playsingle_controller::end_unit_turn(){
if (!browse_)
menu_handler_.end_unit_turn(mouse_handler_, player_number_);
}
void playsingle_controller::user_command(){
menu_handler_.user_command();
}
void playsingle_controller::custom_command(){
menu_handler_.custom_command();
}
void playsingle_controller::ai_formula(){
menu_handler_.ai_formula();
}
void playsingle_controller::clear_messages(){
menu_handler_.clear_messages();
}
void playsingle_controller::whiteboard_toggle() {
whiteboard_manager_->set_active(!whiteboard_manager_->is_active());
if (whiteboard_manager_->is_active()) {
std::string hk = hotkey::get_names(hotkey::hotkey_command::get_command_by_command(hotkey::HOTKEY_WB_TOGGLE).command);
utils::string_map symbols;
symbols["hotkey"] = hk;
gui_->announce(_("Planning mode activated!") + std::string("\n") + vgettext("(press $hotkey to deactivate)", symbols), font::NORMAL_COLOR);
} else {
gui_->announce(_("Planning mode deactivated!"), font::NORMAL_COLOR);
}
//@todo Stop printing whiteboard help in the chat once we have better documentation/help
whiteboard_manager_->print_help_once();
}
void playsingle_controller::whiteboard_execute_action(){
whiteboard_manager_->contextual_execute();
}
void playsingle_controller::whiteboard_execute_all_actions(){
whiteboard_manager_->execute_all_actions();
}
void playsingle_controller::whiteboard_delete_action(){
whiteboard_manager_->contextual_delete();
}
void playsingle_controller::whiteboard_bump_up_action()
{
whiteboard_manager_->contextual_bump_up_action();
}
void playsingle_controller::whiteboard_bump_down_action()
{
whiteboard_manager_->contextual_bump_down_action();
}
void playsingle_controller::whiteboard_suppose_dead()
{
unit* curr_unit;
map_location loc;
{ wb::future_map future; //start planned unit map scope
curr_unit = &*menu_handler_.current_unit();
loc = curr_unit->get_location();
} // end planned unit map scope
whiteboard_manager_->save_suppose_dead(*curr_unit,loc);
}
void playsingle_controller::report_victory(
std::ostringstream &report, int player_gold, int remaining_gold,
int finishing_bonus_per_turn, int turns_left, int finishing_bonus)
{
const end_level_data &end_level = get_end_level_data_const();
report << _("Remaining gold: ")
<< utils::half_signed_value(remaining_gold) << "\n";
if(end_level.gold_bonus) {
if (turns_left > -1) {
report << _("Early finish bonus: ")
<< finishing_bonus_per_turn
<< " " << _("per turn") << "\n"
<< "<b>" << _("Turns finished early: ")
<< turns_left << "</b>\n"
<< _("Bonus: ")
<< finishing_bonus << "\n";
}
report << _("Gold: ")
<< utils::half_signed_value(remaining_gold + finishing_bonus);
}
if (remaining_gold > 0) {
report << '\n' << _("Carry over percentage: ") << end_level.carryover_percentage;
}
if(end_level.carryover_add) {
report << "\n<b>" << _("Bonus Gold: ") << utils::half_signed_value(player_gold) <<"</b>";
} else {
report << "\n<b>" << _("Retained Gold: ") << utils::half_signed_value(player_gold) << "</b>";
}
std::string goldmsg;
utils::string_map symbols;
symbols["gold"] = lexical_cast_default<std::string>(player_gold);
// Note that both strings are the same in English, but some languages will
// want to translate them differently.
if(end_level.carryover_add) {
if(player_gold > 0) {
goldmsg = vngettext(
"You will start the next scenario with $gold "
"on top of the defined minimum starting gold.",
"You will start the next scenario with $gold "
"on top of the defined minimum starting gold.",
player_gold, symbols);
} else {
goldmsg = vngettext(
"You will start the next scenario with "
"the defined minimum starting gold.",
"You will start the next scenario with "
"the defined minimum starting gold.",
player_gold, symbols);
}
} else {
goldmsg = vngettext(
"You will start the next scenario with $gold "
"or its defined minimum starting gold, "
"whichever is higher.",
"You will start the next scenario with $gold "
"or its defined minimum starting gold, "
"whichever is higher.",
player_gold, symbols);
}
// xgettext:no-c-format
report << '\n' << goldmsg;
}
possible_end_play_signal playsingle_controller::play_scenario_init(end_level_data & /*eld*/, bool & past_prestart) {
// At the beginning of the scenario, save a snapshot as replay_start
if(saved_game_.replay_start().empty()){
saved_game_.replay_start() = to_config();
}
HANDLE_END_PLAY_SIGNAL( fire_preload() );
replaying_ = (recorder.at_end() == false);
assert(!replaying_);
if(!loading_game_ )
{
if(replaying_)
{
//can this codepath be reached ?
//note this when we are entering an mp game and see the 'replay' of the game
//this path is not reached because we receive the replay later
config* pstart = recorder.get_next_action();
assert(pstart->has_child("start"));
}
else
{
assert(recorder.empty());
recorder.add_start();
recorder.get_next_action();
}
//we can only use a set_scontext_synced with a non empty recorder.
set_scontext_synced sync;
HANDLE_END_PLAY_SIGNAL( fire_prestart() );
init_gui();
past_prestart = true;
LOG_NG << "first_time..." << (recorder.is_skipping() ? "skipping" : "no skip") << "\n";
events::raise_draw_event();
HANDLE_END_PLAY_SIGNAL( fire_start(true) );
gui_->recalculate_minimap();
}
else
{
init_gui();
past_prestart = true;
events::raise_draw_event();
HANDLE_END_PLAY_SIGNAL( fire_start(false) );
gui_->recalculate_minimap();
}
return boost::none;
}
possible_end_play_signal playsingle_controller::play_scenario_main_loop(end_level_data & end_level, bool & /*past_prestart*/) {
LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks_) << "\n";
// Initialize countdown clock.
std::vector<team>::const_iterator t;
for(t = gamestate_.board_.teams().begin(); t != gamestate_.board_.teams().end(); ++t) {
if (saved_game_.mp_settings().mp_countdown && !loading_game_ ){
t->set_countdown_time(1000 * saved_game_.mp_settings().mp_countdown_init_time);
}
}
// 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;
end_level_struct els = { SKIP_TO_LINGER };
return possible_end_play_signal ( els );
//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_;
ai_testing::log_game_start();
if(gamestate_.board_.teams().empty())
{
ERR_NG << "Playing game with 0 teams." << std::endl;
}
for(; ; first_player_ = 1) {
PROPOGATE_END_PLAY_SIGNAL( play_turn() );
do_autosaves_ = true;
} //end for loop
return boost::none;
}
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);
}
LOG_NG << "Healing survived units\n";
gamestate_.board_.heal_all_survivors();
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;
}
possible_end_play_signal 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_ = first_player_; player_number_ <= int(gamestate_.board_.teams().size()); ++player_number_)
{
// If a side is empty skip over it.
if (current_team().is_empty()) continue;
possible_end_play_signal signal;
{
save_blocker blocker;
signal = init_side();
}
if (signal) {
switch (boost::apply_visitor(get_signal_type(), *signal)) {
case END_TURN:
if (current_team().is_network() == false) {
turn_data_.send_data();
recorder.end_turn();
turn_data_.sync_network();
}
continue;
case END_LEVEL:
return signal;
}
}
if (replaying_) {
LOG_NG << "doing replay " << player_number_ << "\n";
HANDLE_END_PLAY_SIGNAL ( replaying_ = ::do_replay() == REPLAY_FOUND_END_TURN );
LOG_NG << "result of replay: " << (replaying_?"true":"false") << "\n";
} else {
ai_testing::log_turn_start(player_number_);
PROPOGATE_END_PLAY_SIGNAL ( play_side() );
}
finish_side_turn();
if(non_interactive()) {
LOG_AIT << " Player " << player_number_ << ": " <<
current_team().villages().size() << " Villages" <<
std::endl;
ai_testing::log_turn_end(player_number_);
}
HANDLE_END_PLAY_SIGNAL ( check_victory() );
//if loading a savegame, network turns might not have reset this yet
loading_game_ = false;
}
//If the loop exits due to the last team having been processed,
//player_number_ will be 1 too high
if(player_number_ > static_cast<int>(gamestate_.board_.teams().size()))
player_number_ = gamestate_.board_.teams().size();
finish_turn();
// Time has run out
PROPOGATE_END_PLAY_SIGNAL ( check_time_over() );
return boost::none;
}
possible_end_play_signal playsingle_controller::play_idle_loop()
{
while(!end_turn_) {
HANDLE_END_PLAY_SIGNAL( play_slice() );
gui_->draw();
SDL_Delay(10);
}
return boost::none;
}
possible_end_play_signal playsingle_controller::play_side()
{
//check for team-specific items in the scenario
gui_->parse_team_overlays();
HANDLE_END_PLAY_SIGNAL( maybe_do_init_side(false) );
//flag used when we fallback from ai and give temporarily control to human
bool temporary_human = false;
do {
// This flag can be set by derived classes (in overridden functions).
player_type_changed_ = false;
if (!skip_next_turn_)
end_turn_ = false;
statistics::reset_turn_stats(gamestate_.board_.teams()[player_number_ - 1].save_id());
if(current_team().is_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
|| (resources::units->size() == 0 && player_number_ == 1))
{
possible_end_play_signal signal = before_human_turn();
if (!signal) {
signal = play_human_turn();
}
if (signal) {
switch (boost::apply_visitor(get_signal_type(), *signal)) {
case END_LEVEL:
return signal;
case END_TURN:
if (int(boost::apply_visitor(get_redo(),*signal)) == player_number_) {
player_type_changed_ = true;
// If new controller is not human,
// reset gui to prev human one
if (!gamestate_.board_.teams()[player_number_-1].is_human()) {
browse_ = true;
int s = find_human_team_before_current_player();
if (s <= 0)
s = gui_->playing_side();
update_gui_to_player(s-1);
}
}
}
}
}
// Ending the turn commits all moves.
undo_stack_->clear();
if ( !player_type_changed_ )
after_human_turn();
LOG_NG << "human finished turn...\n";
} else if(current_team().is_ai()) {
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;
} catch (end_level_exception & e) { //Don't know at the moment if these two are possible but can't hurt to add
return possible_end_play_signal(e.to_struct());
} catch (end_turn_exception & e) {
return possible_end_play_signal(e.to_struct());
}
if(!player_type_changed_)
{
recorder.end_turn();
}
} else if(current_team().is_network()) {
PROPOGATE_END_PLAY_SIGNAL( play_network_turn() );
} else if(current_team().is_idle()) {
end_turn_enable(false);
do_idle_notification();
possible_end_play_signal signal = before_human_turn();
if (!signal) {
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;
if (int(boost::apply_visitor(get_redo(), *signal)) == player_number_) {
player_type_changed_ = true;
// If new controller is not human,
// reset gui to prev human one
if (!gamestate_.board_.teams()[player_number_-1].is_human()) {
browse_ = true;
int s = find_human_team_before_current_player();
if (s <= 0)
s = gui_->playing_side();
update_gui_to_player(s-1);
}
}
}
}
}
// Else current_team().is_empty(), so do nothing.
} while (player_type_changed_);
// Keep looping if the type of a team (human/ai/networked)
// has changed mid-turn
skip_next_turn_ = false;
return boost::none;
}
possible_end_play_signal playsingle_controller::before_human_turn()
{
log_scope("player turn");
browse_ = false;
linger_ = false;
HANDLE_END_PLAY_SIGNAL( ai::manager::raise_turn_started() ); //This line throws exception from here: https://github.com/wesnoth/wesnoth/blob/ac96a2b91b3276e20b682210617cf87d1e0d366a/src/playsingle_controller.cpp#L954
if(do_autosaves_ && level_result_ == NONE) {
savegame::autosave_savegame save(saved_game_, *gui_, to_config(), preferences::save_compression_format());
save.autosave(game_config::disable_autosave, preferences::autosavemax(), preferences::INFINITE_AUTO_SAVES);
}
if(preferences::turn_bell() && level_result_ == NONE) {
sound::play_bell(game_config::sounds::turn_bell);
}
return boost::none;
}
void playsingle_controller::show_turn_dialog(){
if(preferences::turn_dialog() && (level_result_ == NONE) ) {
blindfold b(*resources::screen, true); //apply a blindfold for the duration of this dialog
resources::screen->redraw_everything();
resources::screen->recalculate_minimap();
std::string message = _("It is now $name|s turn");
utils::string_map symbols;
symbols["name"] = gamestate_.board_.teams()[player_number_ - 1].current_player();
message = utils::interpolate_variables_into_string(message, &symbols);
gui2::show_transient_message(gui_->video(), "", message);
}
}
void playsingle_controller::execute_gotos(){
menu_handler_.execute_gotos(mouse_handler_, player_number_);
}
possible_end_play_signal playsingle_controller::play_human_turn() {
show_turn_dialog();
if (!preferences::disable_auto_moves()) {
HANDLE_END_PLAY_SIGNAL(execute_gotos());
}
end_turn_enable(true);
while(!end_turn_) {
HANDLE_END_PLAY_SIGNAL( play_slice() );
HANDLE_END_PLAY_SIGNAL( check_end_level() );
gui_->draw();
}
return boost::none;
}
void playsingle_controller::linger()
{
LOG_NG << "beginning end-of-scenario linger\n";
browse_ = true;
linger_ = true;
// If we need to set the status depending on the completion state
// the key to it is here.
gui_->set_game_mode(game_display::LINGER_SP);
// this is actually for after linger mode is over -- we don't
// want to stay stuck in linger state when the *next* scenario
// is over.
set_completion setter(saved_game_,"running");
// change the end-turn button text to its alternate label
gui_->get_theme().refresh_title2("button-endturn", "title2");
gui_->invalidate_theme();
gui_->redraw_everything();
// End all unit moves
gamestate_.board_.set_all_units_user_end_turn();
try {
// Same logic as single-player human turn, but
// *not* the same as multiplayer human turn.
end_turn_enable(true);
end_turn_ = false;
while(!end_turn_) {
// Reset the team number to make sure we're the right team.
player_number_ = first_player_;
play_slice();
gui_->draw();
}
} 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;
}
// revert the end-turn button text to its normal label
gui_->get_theme().refresh_title2("button-endturn", "title");
gui_->invalidate_theme();
gui_->redraw_everything();
gui_->set_game_mode(game_display::RUNNING);
LOG_NG << "ending end-of-scenario linger\n";
}
void playsingle_controller::end_turn_enable(bool enable)
{
gui_->enable_menu("endturn", enable);
set_button_state(*gui_);
}
hotkey::ACTION_STATE playsingle_controller::get_action_state(hotkey::HOTKEY_COMMAND command, int index) const
{
switch(command) {
case hotkey::HOTKEY_WB_TOGGLE:
return whiteboard_manager_->is_active() ? hotkey::ACTION_ON : hotkey::ACTION_OFF;
default:
return play_controller::get_action_state(command, index);
}
}
void playsingle_controller::after_human_turn()
{
// Mark the turn as done.
browse_ = true;
if (!linger_)
{
recorder.end_turn();
}
// Clear moves from the GUI.
gui_->set_route(NULL);
gui_->unhighlight_reach();
}
void playsingle_controller::play_ai_turn(){
LOG_NG << "is ai...\n";
end_turn_enable(false);
browse_ = true;
gui_->recalculate_minimap();
const cursor::setter cursor_setter(cursor::WAIT);
// Correct an oddball case where a human could have left delayed shroud
// updates on before giving control to the AI. (The AI does not bother
// with the undo stack, so it cannot delay shroud updates.)
team & cur_team = current_team();
if ( !cur_team.auto_shroud_updates() ) {
// We just took control, so the undo stack is empty. We still need
// to record this change for the replay though.
synced_context::run_in_synced_context("auto_shroud", replay_helper::get_auto_shroud(true));
}
turn_data_.send_data();
turn_info_sync sync_safe(turn_data_);
try {
ai::manager::play_turn(player_number_);
} catch (end_turn_exception&) {
}
gui_->recalculate_minimap();
gui_->invalidate_unit();
gui_->invalidate_game_status();
gui_->invalidate_all();
gui_->draw();
gui_->delay(100);
}
/**
* Will handle sending a networked notification in descendent classes.
*/
void playsingle_controller::do_idle_notification()
{
resources::screen->get_chat_manager().add_chat_message(time(NULL), "Wesnoth", 0,
"This side is in an idle state. To proceed with the game, the host must assign it to another controller.",
events::chat_handler::MESSAGE_PUBLIC, false);
}
/**
* Will handle networked turns in descendent classes.
*/
possible_end_play_signal playsingle_controller::play_network_turn()
{
// There should be no networked sides in single-player.
ERR_NG << "Networked team encountered by playsingle_controller." << std::endl;
return boost::none;
}
void playsingle_controller::handle_generic_event(const std::string& name){
if (name == "ai_user_interact"){
play_slice(false);
}
if (end_turn_){
throw end_turn_exception();
}
}
possible_end_play_signal playsingle_controller::check_time_over(){
bool b = gamestate_.tod_manager_.next_turn(*resources::gamedata);
it_is_a_new_turn_ = true;
if(!b) {
LOG_NG << "firing time over event...\n";
game_events::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 boost::none;
}
if(non_interactive()) {
LOG_AIT << "time over (draw)\n";
ai_testing::log_draw();
}
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);
}
return boost::none;
}
bool playsingle_controller::can_execute_command(const hotkey::hotkey_command& cmd, int index) const
{
hotkey::HOTKEY_COMMAND command = cmd.id;
bool res = true;
switch (command){
case hotkey::HOTKEY_WML:
//code mixed from play_controller::show_menu and code here
return (gui_->viewing_team() == gui_->playing_team()) && !events::commands_disabled && gamestate_.board_.teams()[gui_->viewing_team()].is_human() && !linger_ && !browse_;
case hotkey::HOTKEY_UNIT_HOLD_POSITION:
case hotkey::HOTKEY_END_UNIT_TURN:
return !browse_ && !linger_ && !events::commands_disabled;
case hotkey::HOTKEY_RECRUIT:
case hotkey::HOTKEY_REPEAT_RECRUIT:
case hotkey::HOTKEY_RECALL:
return (!browse_ || whiteboard_manager_->is_active()) && !linger_ && !events::commands_disabled;
case hotkey::HOTKEY_ENDTURN:
return (!browse_ || linger_) && !events::commands_disabled;
case hotkey::HOTKEY_DELAY_SHROUD:
return !linger_ && (gamestate_.board_.teams()[gui_->viewing_team()].uses_fog() || gamestate_.board_.teams()[gui_->viewing_team()].uses_shroud())
&& !events::commands_disabled;
case hotkey::HOTKEY_UPDATE_SHROUD:
return !linger_
&& player_number_ == gui_->viewing_side()
&& !events::commands_disabled
&& gamestate_.board_.teams()[gui_->viewing_team()].auto_shroud_updates() == false;
// Commands we can only do if in debug mode
case hotkey::HOTKEY_CREATE_UNIT:
case hotkey::HOTKEY_CHANGE_SIDE:
case hotkey::HOTKEY_KILL_UNIT:
return !events::commands_disabled && game_config::debug && gamestate_.board_.map().on_board(mouse_handler_.get_last_hex());
case hotkey::HOTKEY_CLEAR_LABELS:
res = !is_observer();
break;
case hotkey::HOTKEY_LABEL_TEAM_TERRAIN:
case hotkey::HOTKEY_LABEL_TERRAIN: {
const terrain_label *label = resources::screen->labels().get_label(mouse_handler_.get_last_hex());
res = !events::commands_disabled && gamestate_.board_.map().on_board(mouse_handler_.get_last_hex())
&& !gui_->shrouded(mouse_handler_.get_last_hex())
&& !is_observer()
&& (!label || !label->immutable());
break;
}
case hotkey::HOTKEY_CONTINUE_MOVE: {
if(browse_ || events::commands_disabled)
return false;
if( (menu_handler_.current_unit().valid())
&& (menu_handler_.current_unit()->move_interrupted()))
return true;
const unit_map::const_iterator i = gamestate_.board_.units().find(mouse_handler_.get_selected_hex());
if (!i.valid()) return false;
return i->move_interrupted();
}
case hotkey::HOTKEY_WB_TOGGLE:
return !is_observer();
case hotkey::HOTKEY_WB_EXECUTE_ACTION:
case hotkey::HOTKEY_WB_EXECUTE_ALL_ACTIONS:
return whiteboard_manager_->can_enable_execution_hotkeys();
case hotkey::HOTKEY_WB_DELETE_ACTION:
return whiteboard_manager_->can_enable_modifier_hotkeys();
case hotkey::HOTKEY_WB_BUMP_UP_ACTION:
case hotkey::HOTKEY_WB_BUMP_DOWN_ACTION:
return whiteboard_manager_->can_enable_reorder_hotkeys();
case hotkey::HOTKEY_WB_SUPPOSE_DEAD:
{
//@todo re-enable this once we figure out a decent UI for suppose_dead
return false;
}
default: return play_controller::can_execute_command(cmd, index);
}
return res;
}
bool playsingle_controller::is_host() const
{
return turn_data_.is_host();
}
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) {
linger();
}
}