mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-11 10:47:04 +00:00
910 lines
26 KiB
C++
910 lines
26 KiB
C++
/* $Id$ */
|
|
|
|
/*
|
|
Copyright (C) 2006 - 2009 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 version 2
|
|
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 playsingle_controller.cpp
|
|
* Logic for single-player game.
|
|
*/
|
|
|
|
#include "playsingle_controller.hpp"
|
|
|
|
#include "ai.hpp"
|
|
#include "game_end_exceptions.hpp"
|
|
#include "gettext.hpp"
|
|
#include "intro.hpp"
|
|
#include "log.hpp"
|
|
#include "map_label.hpp"
|
|
#include "marked-up_text.hpp"
|
|
#include "sound.hpp"
|
|
#include "upload_log.hpp"
|
|
#include "formula_string_utils.hpp"
|
|
|
|
#define ERR_NG LOG_STREAM(err, engine)
|
|
#define LOG_NG LOG_STREAM(info, engine)
|
|
|
|
playsingle_controller::playsingle_controller(const config& level,
|
|
game_state& state_of_game, const int ticks, const int num_turns,
|
|
const config& game_config, CVideo& video, bool skip_replay) :
|
|
play_controller(level, state_of_game, ticks, num_turns, game_config, video, skip_replay),
|
|
cursor_setter(cursor::NORMAL),
|
|
data_backlog_(),
|
|
textbox_info_(),
|
|
replay_sender_(recorder),
|
|
end_turn_(false),
|
|
player_type_changed_(false),
|
|
replaying_(false),
|
|
turn_over_(false),
|
|
skip_next_turn_(false),
|
|
victory_music_(),
|
|
defeat_music_()
|
|
{
|
|
// game may need to start in linger mode
|
|
if (state_of_game.completion == "victory" || state_of_game.completion == "defeat")
|
|
{
|
|
LOG_NG << "Setting linger mode.\n";
|
|
browse_ = linger_ = true;
|
|
}
|
|
}
|
|
|
|
|
|
playsingle_controller::~playsingle_controller()
|
|
{
|
|
ai_manager::reap_ais() ;
|
|
}
|
|
|
|
|
|
void playsingle_controller::init_gui(){
|
|
LOG_NG << "Initializing GUI... " << (SDL_GetTicks() - ticks_) << "\n";
|
|
play_controller::init_gui();
|
|
|
|
if(first_human_team_ != -1) {
|
|
gui_->scroll_to_tile(map_.starting_position(first_human_team_ + 1), game_display::WARP);
|
|
}
|
|
gui_->scroll_to_tile(map_.starting_position(1), game_display::WARP);
|
|
|
|
update_locker lock_display(gui_->video(),recorder.is_skipping());
|
|
events::raise_draw_event();
|
|
gui_->draw();
|
|
for(std::vector<team>::iterator t = teams_.begin(); t != teams_.end(); ++t) {
|
|
::clear_shroud(*gui_,map_,units_,teams_,(t-teams_.begin()));
|
|
}
|
|
}
|
|
|
|
void playsingle_controller::recruit(){
|
|
if (!browse_)
|
|
menu_handler_.recruit(browse_, player_number_, mouse_handler_.get_last_hex());
|
|
}
|
|
|
|
void playsingle_controller::repeat_recruit(){
|
|
if (!browse_)
|
|
menu_handler_.repeat_recruit(player_number_, mouse_handler_.get_last_hex());
|
|
}
|
|
|
|
void playsingle_controller::recall(){
|
|
if (!browse_)
|
|
menu_handler_.recall(player_number_, mouse_handler_.get_last_hex());
|
|
}
|
|
|
|
void playsingle_controller::toggle_shroud_updates(){
|
|
menu_handler_.toggle_shroud_updates(player_number_);
|
|
}
|
|
|
|
void playsingle_controller::update_shroud_now(){
|
|
menu_handler_.update_shroud_now(player_number_);
|
|
}
|
|
|
|
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::rename_unit(){
|
|
menu_handler_.rename_unit(mouse_handler_);
|
|
}
|
|
|
|
void playsingle_controller::create_unit(){
|
|
menu_handler_.create_unit(mouse_handler_);
|
|
}
|
|
|
|
void playsingle_controller::change_unit_side(){
|
|
menu_handler_.change_unit_side(mouse_handler_);
|
|
}
|
|
|
|
void playsingle_controller::label_terrain(bool team_only){
|
|
menu_handler_.label_terrain(mouse_handler_, team_only);
|
|
}
|
|
|
|
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(mouse_handler_, player_number_);
|
|
}
|
|
|
|
void playsingle_controller::ai_formula(){
|
|
menu_handler_.ai_formula();
|
|
}
|
|
|
|
void playsingle_controller::clear_messages(){
|
|
menu_handler_.clear_messages();
|
|
}
|
|
|
|
#ifdef USRCMD2
|
|
void playsingle_controller::user_command_2(){
|
|
menu_handler_.user_command_2();
|
|
}
|
|
|
|
void playsingle_controller::user_command_3(){
|
|
menu_handler_.user_command_3();
|
|
}
|
|
#endif
|
|
|
|
void playsingle_controller::report_victory(
|
|
std::stringstream& report,
|
|
end_level_exception& end_level,
|
|
int player_gold,
|
|
int remaining_gold, int finishing_bonus_per_turn,
|
|
int turns_left, int finishing_bonus)
|
|
{
|
|
report << _("Remaining gold: ")
|
|
<< remaining_gold << "\n";
|
|
if(end_level.gold_bonus) {
|
|
if (turns_left > -1) {
|
|
report << _("Early finish bonus: ")
|
|
<< finishing_bonus_per_turn
|
|
<< " " << _("per turn") << "\n"
|
|
<< font::BOLD_TEXT << _("Turns finished early: ")
|
|
<< turns_left << "\n"
|
|
<< _("Bonus: ")
|
|
<< finishing_bonus << "\n";
|
|
}
|
|
report << _("Gold: ")
|
|
<< (remaining_gold + finishing_bonus);
|
|
}
|
|
if (remaining_gold > 0) {
|
|
report << '\n' << _("Carry over percentage: ") << end_level.carryover_percentage;
|
|
}
|
|
if(end_level.carryover_add) {
|
|
report << '\n' << font::BOLD_TEXT << _("Bonus Gold: ") << player_gold;
|
|
} else {
|
|
report << '\n' << font::BOLD_TEXT << _("Retained Gold: ") << player_gold;
|
|
}
|
|
|
|
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) {
|
|
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 $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;
|
|
}
|
|
|
|
LEVEL_RESULT playsingle_controller::play_scenario(const std::vector<config*>& story, upload_log& log,
|
|
bool skip_replay, end_level_exception* end_level_result)
|
|
{
|
|
LOG_NG << "in playsingle_controller::play_scenario()...\n";
|
|
|
|
// Start music.
|
|
const config::child_list& m = level_.get_children("music");
|
|
config::const_child_iterator i;
|
|
for (i = m.begin(); i != m.end(); i++) {
|
|
sound::play_music_config(**i);
|
|
}
|
|
sound::commit_music_changes();
|
|
|
|
if(!skip_replay) {
|
|
for(std::vector<config*>::const_iterator story_i = story.begin(); story_i != story.end(); ++story_i) {
|
|
show_intro(*gui_,vconfig(*story_i, *story_i), level_);
|
|
}
|
|
}
|
|
gui_->labels().read(level_, game_events::get_state_of_game());
|
|
|
|
// Find a list of 'items' (i.e. overlays) on the level, and add them
|
|
const config::child_list& overlays = level_.get_children("item");
|
|
for(config::child_list::const_iterator overlay = overlays.begin(); overlay != overlays.end(); ++overlay) {
|
|
gui_->add_overlay(map_location(**overlay, game_events::get_state_of_game()), (**overlay)["image"], (**overlay)["halo"], (**overlay)["team_name"], (**overlay)["fogged"]);
|
|
}
|
|
|
|
// Read sound sources
|
|
assert(soundsources_manager_ != NULL);
|
|
const config::child_list& snd_sources = level_.get_children("sound_source");
|
|
for(config::child_list::const_iterator i = snd_sources.begin(); i != snd_sources.end(); ++i) {
|
|
assert(*i != NULL);
|
|
soundsource::sourcespec spec(**i);
|
|
soundsources_manager_->add(spec);
|
|
}
|
|
|
|
victory_conditions::set_victory_when_enemies_defeated(
|
|
level_["victory_when_enemies_defeated"] != "no");
|
|
victory_conditions::set_carryover_percentage(
|
|
lexical_cast_default<int>(level_["carryover_percentage"],
|
|
game_config::gold_carryover_percentage));
|
|
victory_conditions::set_carryover_add(utils::string_bool(
|
|
level_["carryover_add"], game_config::gold_carryover_add));
|
|
|
|
LOG_NG << "entering try... " << (SDL_GetTicks() - ticks_) << "\n";
|
|
try {
|
|
// Log before prestart events: they do weird things.
|
|
if (first_human_team_ != -1) {
|
|
log.start(gamestate_, teams_[first_human_team_], first_human_team_ + 1, units_,
|
|
loading_game_ ? gamestate_.get_variable("turn_number") : "", status_.number_of_turns());
|
|
}
|
|
|
|
fire_prestart(!loading_game_);
|
|
init_gui();
|
|
|
|
LOG_NG << "first_time..." << (recorder.is_skipping() ? "skipping" : "no skip") << "\n";
|
|
|
|
fire_start(!loading_game_);
|
|
gui_->recalculate_minimap();
|
|
|
|
replaying_ = (recorder.at_end() == false);
|
|
|
|
LOG_NG << "starting main loop\n" << (SDL_GetTicks() - ticks_) << "\n";
|
|
|
|
// Initialize countdown clock.
|
|
std::vector<team>::iterator t;
|
|
for(t = teams_.begin(); t != teams_.end(); ++t) {
|
|
std::string countd_enabled = level_["mp_countdown"].c_str();
|
|
if (utils::string_bool(countd_enabled) && !loading_game_ ){
|
|
t->set_countdown_time(1000 * lexical_cast_default<int>(level_["mp_countdown_init_time"],0));
|
|
}
|
|
}
|
|
|
|
// if we loaded a save file in linger mode, skip to it.
|
|
if (linger_)
|
|
throw end_level_exception(SKIP_TO_LINGER);
|
|
|
|
// Avoid autosaving after loading, but still
|
|
// allow the first turn to have an autosave.
|
|
bool save = !loading_game_;
|
|
for(; ; first_player_ = 1) {
|
|
play_turn(save);
|
|
save = true;
|
|
} //end for loop
|
|
|
|
} catch(game::load_game_exception&) {
|
|
// Loading a new game is effectively a quit.
|
|
log.quit(status_.turn());
|
|
throw;
|
|
} catch(end_level_exception& end_level) {
|
|
*end_level_result = end_level;
|
|
if(!end_level.custom_endlevel_music.empty()) {
|
|
switch(end_level.result) {
|
|
case DEFEAT:
|
|
set_defeat_music_list(end_level.custom_endlevel_music);
|
|
break;
|
|
default:
|
|
set_victory_music_list(end_level.custom_endlevel_music);
|
|
}
|
|
}
|
|
|
|
if(!team_manager_.get_teams().size())
|
|
{
|
|
return VICTORY; // this is probably only a story scenario, i.e. has its endlevel in the prestart event
|
|
}
|
|
const bool obs = team_manager_.is_observer();
|
|
if (game_config::exit_at_end) {
|
|
exit(0);
|
|
}
|
|
if (end_level.result == DEFEAT || end_level.result == VICTORY) {
|
|
gamestate_.completion = (end_level.result == VICTORY) ? "victory" : "defeat";
|
|
recorder.set_save_info_completion(gamestate_.completion);
|
|
// 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"] = gamestate_.completion;
|
|
network::send_data(cfg, 0, true);
|
|
} else {
|
|
gui::message_dialog(*gui_,_("Game Over"),
|
|
_("The game is over.")).show();
|
|
return OBSERVER_END;
|
|
}
|
|
}
|
|
|
|
if(end_level.result == QUIT) {
|
|
log.quit(status_.turn());
|
|
return end_level.result;
|
|
} else if(end_level.result == DEFEAT) {
|
|
gamestate_.completion = "defeat";
|
|
log.defeat(status_.turn());
|
|
try {
|
|
game_events::fire("defeat");
|
|
} catch(end_level_exception&) {
|
|
ERR_NG << "[endlevel] used in 'defeat' event handler\n";
|
|
}
|
|
|
|
if (!obs) {
|
|
const std::string& defeat_music = select_defeat_music();
|
|
if(defeat_music.empty() != true)
|
|
sound::play_music_once(defeat_music);
|
|
|
|
return DEFEAT;
|
|
} else {
|
|
return QUIT;
|
|
}
|
|
} else if (end_level.result == VICTORY)
|
|
{
|
|
gamestate_.completion = (!end_level.linger_mode ?
|
|
"running" : "victory");
|
|
recorder.set_save_info_completion(gamestate_.completion);
|
|
try {
|
|
game_events::fire("victory");
|
|
} catch(end_level_exception&) {
|
|
ERR_NG << "[endlevel] used in 'victory' event handler\n";
|
|
}
|
|
//
|
|
// 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(end_level.result == VICTORY && !obs) {
|
|
const std::string& victory_music = select_victory_music();
|
|
if(victory_music.empty() != true)
|
|
sound::play_music_once(victory_music);
|
|
}
|
|
if (first_human_team_ != -1)
|
|
log.victory(status_.turn(), teams_[first_human_team_].gold());
|
|
|
|
const bool has_next_scenario = !gamestate_.next_scenario.empty() &&
|
|
gamestate_.next_scenario != "null";
|
|
|
|
// Save current_player name to reuse it when setting next_scenario side info
|
|
std::vector<team>::iterator i;
|
|
for (i = teams_.begin(); i != teams_.end(); ++i) {
|
|
player_info *player=gamestate_.get_player(i->save_id());
|
|
if (player)
|
|
player->name = i->current_player();
|
|
}
|
|
|
|
// Add all the units that survived the scenario.
|
|
LOG_NG << "Add units that survived the scenario to the recall list.\n";
|
|
for(unit_map::iterator un = units_.begin(); un != units_.end(); ++un) {
|
|
player_info *player=gamestate_.get_player(teams_[un->second.side()-1].save_id());
|
|
|
|
if(player) {
|
|
LOG_NG << "Added unit " << un->second.id() << ", " << un->second.name() << "\n";
|
|
un->second.new_turn();
|
|
un->second.new_scenario();
|
|
player->available_units.push_back(un->second);
|
|
}
|
|
}
|
|
|
|
std::stringstream report;
|
|
std::string title;
|
|
|
|
if (obs) {
|
|
title = _("Scenario Report");
|
|
} else {
|
|
title = _("Victory");
|
|
report << font::BOLD_TEXT << _("You have emerged victorious!") << "\n~\n";
|
|
}
|
|
if (gamestate_.players.size() > 0 &&
|
|
(has_next_scenario ||
|
|
gamestate_.campaign_type == "test")) {
|
|
const int finishing_bonus_per_turn =
|
|
map_.villages().size() * game_config::village_income +
|
|
game_config::base_income;
|
|
const int turns_left = std::max<int>(0,status_.number_of_turns() - status_.turn());
|
|
const int finishing_bonus = (end_level.gold_bonus && (turns_left > -1)) ?
|
|
(finishing_bonus_per_turn * turns_left) : 0;
|
|
|
|
for(i=teams_.begin(); i!=teams_.end(); ++i) {
|
|
player_info *player=gamestate_.get_player(i->save_id());
|
|
|
|
if (player) {
|
|
// Store the gold for all players.
|
|
player->gold = ((i->gold() + finishing_bonus)
|
|
* end_level.carryover_percentage) / 100;
|
|
player->gold_add = end_level.carryover_add;
|
|
|
|
// Only show the report for ourselves.
|
|
if (!i->is_local())
|
|
continue;
|
|
|
|
if(gamestate_.players.size()>1) {
|
|
if(i!=teams_.begin()) {
|
|
report << "\n";
|
|
}
|
|
|
|
report << font::BOLD_TEXT << i->current_player() << "\n";
|
|
}
|
|
|
|
report_victory(report, end_level, player->gold, i->gold(), finishing_bonus_per_turn, turns_left, finishing_bonus);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(end_level.carryover_report)
|
|
{
|
|
gui::message_dialog(*gui_, title, report.str()).show();
|
|
}
|
|
|
|
return VICTORY;
|
|
} else if (end_level.result == SKIP_TO_LINGER) {
|
|
LOG_NG << "resuming from loaded linger state...\n";
|
|
return VICTORY;
|
|
}
|
|
} // end catch
|
|
catch(replay::error&) {
|
|
gui::message_dialog(*gui_,"",_("The file you have tried to load is corrupt")).show();
|
|
return QUIT;
|
|
}
|
|
catch(network::error& e) {
|
|
bool disconnect = false;
|
|
if(e.socket) {
|
|
e.disconnect();
|
|
disconnect = true;
|
|
}
|
|
|
|
menu_handler_.save_game(_("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;
|
|
}
|
|
|
|
void playsingle_controller::play_turn(bool save)
|
|
{
|
|
gui_->new_turn();
|
|
gui_->invalidate_game_status();
|
|
events::raise_draw_event();
|
|
|
|
LOG_NG << "turn: " << status_.turn() << "\n";
|
|
|
|
if(non_interactive())
|
|
std::cout << "Turn " << status_.turn() << ":" << std::endl;
|
|
|
|
|
|
for(player_number_ = first_player_; player_number_ <= teams_.size(); player_number_++) {
|
|
// If a side is empty skip over it.
|
|
if (current_team().is_empty()) continue;
|
|
try {
|
|
if (skip_next_turn_) {
|
|
skip_next_turn_ = false;
|
|
throw end_turn_exception();
|
|
}
|
|
init_side(player_number_ - 1);
|
|
} catch (end_turn_exception) {
|
|
if (current_team().is_network() == false) {
|
|
turn_info turn_data(gamestate_, status_, *gui_, map_,
|
|
teams_, player_number_, units_, replay_sender_, undo_stack_);
|
|
recorder.end_turn();
|
|
turn_data.sync_network();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (replaying_) {
|
|
LOG_NG << "doing replay " << player_number_ << "\n";
|
|
try {
|
|
replaying_ = ::do_replay(*gui_, map_, units_, teams_,
|
|
player_number_, status_, gamestate_);
|
|
} catch(replay::error&) {
|
|
gui::message_dialog(*gui_,"",_("The file you have tried to load is corrupt")).show();
|
|
|
|
replaying_ = false;
|
|
}
|
|
LOG_NG << "result of replay: " << (replaying_?"true":"false") << "\n";
|
|
} else {
|
|
// If a side is dead end the turn.
|
|
if ((current_team().is_human() && team_units(units_, player_number_) == 0))
|
|
{
|
|
turn_info turn_data(gamestate_, status_, *gui_, map_,
|
|
teams_, player_number_, units_, replay_sender_, undo_stack_);
|
|
recorder.end_turn();
|
|
turn_data.sync_network();
|
|
continue;
|
|
}
|
|
play_side(player_number_, save);
|
|
}
|
|
|
|
finish_side_turn();
|
|
|
|
if(non_interactive()) {
|
|
std::cout << " Player " << player_number_ << ": " <<
|
|
current_team().villages().size() << " Villages" <<
|
|
std::endl;
|
|
}
|
|
|
|
check_victory(units_, teams_, *gui_);
|
|
}
|
|
|
|
// Time has run out
|
|
check_time_over();
|
|
|
|
try {
|
|
finish_turn();
|
|
} catch (end_turn_exception) {
|
|
// If [end_turn] is encountered, skip the first existing side's turn.
|
|
skip_next_turn_ = true;
|
|
}
|
|
}
|
|
|
|
void playsingle_controller::play_side(const unsigned int team_index, bool save)
|
|
{
|
|
//check for team-specific items in the scenario
|
|
gui_->parse_team_overlays();
|
|
|
|
//flag used when we fallback from ai and give temporarily control to human
|
|
bool temporary_human = false;
|
|
do {
|
|
// Although this flag is used only in this method,
|
|
// it has to be a class member since derived classes
|
|
// rely on it
|
|
player_type_changed_ = false;
|
|
end_turn_ = false;
|
|
|
|
|
|
statistics::reset_turn_stats(teams_[team_index - 1].save_id());
|
|
|
|
if(current_team().is_human() || temporary_human) {
|
|
LOG_NG << "is human...\n";
|
|
try{
|
|
before_human_turn(save);
|
|
play_human_turn();
|
|
after_human_turn();
|
|
} catch(end_turn_exception& end_turn) {
|
|
if (end_turn.redo == team_index) {
|
|
player_type_changed_ = true;
|
|
// If new controller is not human,
|
|
// reset gui to prev human one
|
|
if (!teams_[team_index-1].is_human()) {
|
|
browse_ = true;
|
|
int t = find_human_team_before(team_index);
|
|
if (t > 0) {
|
|
gui_->set_team(t-1);
|
|
gui_->recalculate_minimap();
|
|
gui_->invalidate_all();
|
|
gui_->draw(true,true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(game_config::debug)
|
|
game_display::clear_debug_highlights();
|
|
|
|
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 human for the rest of this turn
|
|
player_type_changed_ = true;
|
|
temporary_human = true;
|
|
} catch(end_turn_exception& end_turn) {
|
|
if (end_turn.redo == team_index) {
|
|
player_type_changed_ = true;
|
|
}
|
|
}
|
|
}
|
|
} while (player_type_changed_);
|
|
// Keep looping if the type of a team (human/ai/networked)
|
|
// has changed mid-turn
|
|
}
|
|
|
|
void playsingle_controller::before_human_turn(bool save)
|
|
{
|
|
log_scope("player turn");
|
|
browse_ = false;
|
|
linger_ = false;
|
|
|
|
gui_->set_team(player_number_ - 1);
|
|
gui_->recalculate_minimap();
|
|
gui_->invalidate_all();
|
|
gui_->draw(true,true);
|
|
|
|
if (save) {
|
|
menu_handler_.autosave(gamestate_.label, status_.turn(), gamestate_.starting_pos);
|
|
}
|
|
|
|
if(preferences::turn_bell()) {
|
|
sound::play_bell(game_config::sounds::turn_bell);
|
|
}
|
|
}
|
|
|
|
void playsingle_controller::show_turn_dialog(){
|
|
if(preferences::turn_dialog()) {
|
|
std::string message = _("It is now $name|'s turn");
|
|
utils::string_map symbols;
|
|
symbols["name"] = teams_[player_number_ - 1].current_player();
|
|
message = utils::interpolate_variables_into_string(message, &symbols);
|
|
gui::message_dialog(*gui_, "", message).show();
|
|
}
|
|
}
|
|
|
|
void playsingle_controller::execute_gotos(){
|
|
// Execute goto-movements - first collect gotos in a list
|
|
std::vector<map_location> gotos;
|
|
|
|
for(unit_map::iterator ui = units_.begin(); ui != units_.end(); ++ui) {
|
|
if(ui->second.get_goto() == ui->first)
|
|
ui->second.set_goto(map_location());
|
|
|
|
if(ui->second.side() == player_number_ && map_.on_board(ui->second.get_goto()))
|
|
gotos.push_back(ui->first);
|
|
}
|
|
|
|
for(std::vector<map_location>::const_iterator g = gotos.begin(); g != gotos.end(); ++g) {
|
|
unit_map::const_iterator ui = units_.find(*g);
|
|
menu_handler_.move_unit_to_loc(ui,ui->second.get_goto(),false, player_number_, mouse_handler_);
|
|
}
|
|
|
|
// erase the footsteps after movement
|
|
gui_->set_route(NULL);
|
|
}
|
|
|
|
void playsingle_controller::play_human_turn() {
|
|
show_turn_dialog();
|
|
execute_gotos();
|
|
|
|
gui_->enable_menu("endturn", true);
|
|
while(!end_turn_) {
|
|
play_slice();
|
|
gui_->draw();
|
|
}
|
|
}
|
|
struct set_completion
|
|
{
|
|
set_completion(game_state& state, const std::string& completion) :
|
|
state_(state), completion_(completion)
|
|
{
|
|
}
|
|
~set_completion()
|
|
{
|
|
state_.completion = completion_;
|
|
}
|
|
private:
|
|
game_state& state_;
|
|
const std::string completion_;
|
|
};
|
|
|
|
void playsingle_controller::linger(upload_log& log)
|
|
{
|
|
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(gamestate_,"running");
|
|
|
|
// change the end-turn button text to its alternate label
|
|
gui_->get_theme().refresh_title2(std::string("button-endturn"), std::string("title2"));
|
|
gui_->invalidate_theme();
|
|
gui_->redraw_everything();
|
|
|
|
// End all unit moves
|
|
for (unit_map::iterator u = units_.begin(); u != units_.end(); u++) {
|
|
u->second.set_user_end_turn(true);
|
|
}
|
|
try {
|
|
// Same logic as single-player human turn, but
|
|
// *not* the same as multiplayer human turn.
|
|
gui_->enable_menu("endturn", true);
|
|
while(!end_turn_) {
|
|
// Reset the team number to make sure we're the right team.
|
|
player_number_ = first_player_;
|
|
play_slice();
|
|
|
|
gui_->draw();
|
|
}
|
|
} catch(game::load_game_exception&) {
|
|
// Loading a new game is effectively a quit.
|
|
log.quit(status_.turn());
|
|
throw;
|
|
}
|
|
|
|
// revert the end-turn button text to its normal label
|
|
gui_->get_theme().refresh_title2(std::string("button-endturn"), std::string("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_record()
|
|
{
|
|
if (!turn_over_)
|
|
{
|
|
turn_over_ = true;
|
|
recorder.end_turn();
|
|
}
|
|
}
|
|
void playsingle_controller::end_turn_record_unlock()
|
|
{
|
|
turn_over_ = false;
|
|
}
|
|
|
|
void playsingle_controller::after_human_turn(){
|
|
browse_ = true;
|
|
end_turn_record();
|
|
end_turn_record_unlock();
|
|
menu_handler_.clear_undo_stack(player_number_);
|
|
|
|
if(teams_[player_number_-1].uses_fog()) {
|
|
// needed because currently fog is only recalculated when a hex is /un/covered
|
|
recalculate_fog(map_,units_,teams_,player_number_-1);
|
|
}
|
|
|
|
gui_->set_route(NULL);
|
|
gui_->unhighlight_reach();
|
|
}
|
|
|
|
void playsingle_controller::play_ai_turn(){
|
|
LOG_NG << "is ai...\n";
|
|
gui_->enable_menu("endturn", false);
|
|
browse_ = true;
|
|
gui_->recalculate_minimap();
|
|
|
|
const cursor::setter cursor_setter(cursor::WAIT);
|
|
|
|
turn_info turn_data(gamestate_,status_,*gui_,
|
|
map_, teams_, player_number_, units_, replay_sender_, undo_stack_);
|
|
|
|
ai_interface::info ai_info(*gui_,map_,units_,teams_,player_number_,status_, turn_data, gamestate_);
|
|
std::string ai_algorithm = current_team().ai_algorithm();
|
|
|
|
boost::intrusive_ptr<ai_interface> ai_obj ;
|
|
ai_obj = ai_manager::get_ai( ai_algorithm, ai_info ) ;
|
|
|
|
ai_obj->user_interact().attach_handler(this);
|
|
ai_obj->unit_recruited().attach_handler(this);
|
|
ai_obj->unit_moved().attach_handler(this);
|
|
ai_obj->enemy_attacked().attach_handler(this);
|
|
try {
|
|
ai_obj->play_turn();
|
|
} catch (end_turn_exception) {
|
|
}
|
|
recorder.end_turn();
|
|
turn_data.sync_network();
|
|
|
|
gui_->recalculate_minimap();
|
|
::clear_shroud(*gui_,map_,units_,teams_,player_number_-1);
|
|
gui_->invalidate_unit();
|
|
gui_->invalidate_game_status();
|
|
gui_->invalidate_all();
|
|
gui_->draw();
|
|
gui_->delay(100);
|
|
}
|
|
|
|
void playsingle_controller::handle_generic_event(const std::string& name){
|
|
if (name == "ai_user_interact"){
|
|
play_slice();
|
|
gui_->draw();
|
|
}
|
|
}
|
|
|
|
void playsingle_controller::check_time_over(){
|
|
if(!status_.next_turn()) {
|
|
|
|
if(non_interactive()) {
|
|
std::cout << "time over (draw)\n";
|
|
}
|
|
|
|
LOG_NG << "firing time over event...\n";
|
|
game_events::fire("time over");
|
|
LOG_NG << "done firing time over event...\n";
|
|
|
|
throw end_level_exception(DEFEAT);
|
|
}
|
|
}
|
|
|
|
bool playsingle_controller::can_execute_command(hotkey::HOTKEY_COMMAND command, int index) const
|
|
{
|
|
bool res = true;
|
|
switch (command){
|
|
case hotkey::HOTKEY_UNIT_HOLD_POSITION:
|
|
case hotkey::HOTKEY_END_UNIT_TURN:
|
|
case hotkey::HOTKEY_RECRUIT:
|
|
case hotkey::HOTKEY_REPEAT_RECRUIT:
|
|
case hotkey::HOTKEY_RECALL:
|
|
return !browse_ && !linger_ && !events::commands_disabled;
|
|
case hotkey::HOTKEY_ENDTURN:
|
|
return (!browse_ || linger_) && !events::commands_disabled;
|
|
|
|
case hotkey::HOTKEY_DELAY_SHROUD:
|
|
return !linger_ && (current_team().uses_fog() || current_team().uses_shroud())
|
|
&& !events::commands_disabled;
|
|
case hotkey::HOTKEY_UPDATE_SHROUD:
|
|
return !linger_ && !events::commands_disabled && current_team().auto_shroud_updates() == false;
|
|
|
|
// Commands we can only do if in debug mode
|
|
case hotkey::HOTKEY_CREATE_UNIT:
|
|
case hotkey::HOTKEY_CHANGE_UNIT_SIDE:
|
|
return !events::commands_disabled && game_config::debug && map_.on_board(mouse_handler_.get_last_hex());
|
|
|
|
case hotkey::HOTKEY_LABEL_TEAM_TERRAIN:
|
|
case hotkey::HOTKEY_LABEL_TERRAIN:
|
|
res = !events::commands_disabled && map_.on_board(mouse_handler_.get_last_hex())
|
|
&& !gui_->shrouded(mouse_handler_.get_last_hex())
|
|
&& !is_observer();
|
|
break;
|
|
|
|
case hotkey::HOTKEY_CONTINUE_MOVE: {
|
|
if(browse_ || events::commands_disabled)
|
|
return false;
|
|
|
|
if( (menu_handler_.current_unit(mouse_handler_) != units_.end())
|
|
&& (menu_handler_.current_unit(mouse_handler_)->second.move_interrupted()))
|
|
return true;
|
|
const unit_map::const_iterator i = units_.find(mouse_handler_.get_selected_hex());
|
|
if (i == units_.end()) return false;
|
|
return i->second.move_interrupted();
|
|
}
|
|
default: return play_controller::can_execute_command(command, index);
|
|
}
|
|
return res;
|
|
}
|