playturn.cpp

Go to the documentation of this file.
00001 /* $Id: playturn.cpp 52533 2012-01-07 02:35:17Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 #include "playturn.hpp"
00017 
00018 #include "construct_dialog.hpp"
00019 #include "foreach.hpp"
00020 #include "game_display.hpp"
00021 #include "game_end_exceptions.hpp"
00022 #include "game_preferences.hpp"
00023 #include "gettext.hpp"
00024 #include "gui/dialogs/simple_item_selector.hpp"
00025 #include "log.hpp"
00026 #include "map_label.hpp"
00027 #include "replay.hpp"
00028 #include "resources.hpp"
00029 #include "rng.hpp"
00030 #include "whiteboard/manager.hpp"
00031 #include "formula_string_utils.hpp"
00032 #include "play_controller.hpp"
00033 
00034 #include <ctime>
00035 
00036 static lg::log_domain log_network("network");
00037 #define ERR_NW LOG_STREAM(err, log_network)
00038 
00039 turn_info::turn_info(unsigned team_num, replay_network_sender &replay_sender) :
00040     team_num_(team_num),
00041     replay_sender_(replay_sender),
00042     host_transfer_("host_transfer"),
00043     replay_()
00044 {
00045     /**
00046      * We do network sync so [init_side] is transferred to network hosts
00047      */
00048     if(network::nconnections() > 0)
00049         send_data();
00050 }
00051 
00052 turn_info::~turn_info(){
00053     resources::undo_stack->clear();
00054 }
00055 
00056 void turn_info::sync_network()
00057 {
00058     if(network::nconnections() > 0) {
00059 
00060         //receive data first, and then send data. When we sent the end of
00061         //the AI's turn, we don't want there to be any chance where we
00062         //could get data back pertaining to the next turn.
00063         config cfg;
00064         while(network::connection res = network::receive_data(cfg)) {
00065             std::deque<config> backlog;
00066             process_network_data(cfg,res,backlog,false);
00067             cfg.clear();
00068         }
00069 
00070         send_data();
00071     }
00072 }
00073 
00074 void turn_info::send_data()
00075 {
00076     if(resources::undo_stack->empty()) {
00077         replay_sender_.commit_and_sync();
00078     } else {
00079         replay_sender_.sync_non_undoable();
00080     }
00081 }
00082 
00083 void turn_info::handle_turn(
00084     bool& turn_end,
00085     const config& t,
00086     const bool skip_replay,
00087     std::deque<config>& backlog)
00088 {
00089     if(turn_end == false) {
00090         /** @todo FIXME: Check what commands we execute when it's our turn! */
00091         replay_.append(t);
00092         replay_.set_skip(skip_replay);
00093 
00094         turn_end = do_replay(team_num_, &replay_);
00095         recorder.add_config(t, replay::MARK_AS_SENT);
00096     } else {
00097 
00098         //this turn has finished, so push the remaining moves
00099         //into the backlog
00100         backlog.push_back(config());
00101         backlog.back().add_child("turn", t);
00102     }
00103 }
00104 
00105 turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg,
00106         network::connection from, std::deque<config>& backlog, bool skip_replay)
00107 {
00108     if (const config &rnd_seed = cfg.child("random_seed")) {
00109         rand_rng::set_seed(rnd_seed["seed"]);
00110         //may call a callback function, see rand_rng::set_seed_callback
00111     }
00112 
00113     if (const config &msg = cfg.child("message"))
00114     {
00115         resources::screen->add_chat_message(time(NULL), msg["sender"], msg["side"],
00116                 msg["message"], events::chat_handler::MESSAGE_PUBLIC,
00117                 preferences::message_bell());
00118     }
00119 
00120     if (const config &msg = cfg.child("whisper") /*&& is_observer()*/)
00121     {
00122         resources::screen->add_chat_message(time(NULL), "whisper: " + msg["sender"].str(), 0,
00123                 msg["message"], events::chat_handler::MESSAGE_PRIVATE,
00124                 preferences::message_bell());
00125     }
00126 
00127     foreach (const config &ob, cfg.child_range("observer")) {
00128         resources::screen->add_observer(ob["name"]);
00129     }
00130 
00131     foreach (const config &ob, cfg.child_range("observer_quit")) {
00132         resources::screen->remove_observer(ob["name"]);
00133     }
00134 
00135     if (cfg.child("leave_game")) {
00136         throw network::error("");
00137     }
00138 
00139     bool turn_end = false;
00140 
00141     config::const_child_itors turns = cfg.child_range("turn");
00142     if (turns.first != turns.second && from != network::null_connection) {
00143         //forward the data to other peers
00144         network::send_data_all_except(cfg, from);
00145     }
00146 
00147     const config& change = cfg.child_or_empty("change_controller");
00148     const std::string& side_drop = cfg["side_drop"].str();
00149 
00150     foreach (const config &t, turns)
00151     {
00152         handle_turn(turn_end, t, skip_replay, backlog);
00153     }
00154 
00155     resources::whiteboard->process_network_data(cfg);
00156 
00157     if (!change.empty())
00158     {
00159         //don't use lexical_cast_default it's "safer" to end on error
00160         const int side = lexical_cast<int>(change["side"]);
00161         const size_t index = static_cast<size_t>(side-1);
00162 
00163         const std::string &controller = change["controller"];
00164         const std::string &player = change["player"];
00165 
00166         if(index < resources::teams->size()) {
00167             team &tm = (*resources::teams)[index];
00168             if (!player.empty())
00169                 tm.set_current_player(player);
00170             unit_map::iterator leader = resources::units->find_leader(side);
00171             bool restart = resources::screen->playing_side() == side;
00172             if (!player.empty() && leader.valid())
00173                 leader->rename(player);
00174 
00175             if (controller == "human" && !tm.is_human()) {
00176                 if (!(*resources::teams)[resources::screen->playing_team()].is_human())
00177                 {
00178                     resources::screen->set_team(index);
00179                 }
00180                 tm.make_human();
00181             } else if (controller == "human_ai" && !tm.is_human_ai()) {
00182                 tm.make_human_ai();
00183             } else if (controller == "network" && !tm.is_network_human()) {
00184                 tm.make_network();
00185             } else if (controller == "network_ai" && !tm.is_network_ai()) {
00186                 tm.make_network_ai();
00187             } else if (controller == "ai" && !tm.is_ai()) {
00188                 tm.make_ai();
00189             }
00190             else
00191             {
00192                 restart = false;
00193             }
00194 
00195             resources::controller->maybe_do_init_side(index);
00196 
00197             resources::whiteboard->on_change_controller(side,tm);
00198 
00199             resources::screen->labels().recalculate_labels();
00200 
00201             return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
00202         }
00203     }
00204 
00205     //if a side has dropped out of the game.
00206     if(!side_drop.empty()) {
00207         const std::string controller = cfg["controller"];
00208         int side = atoi(side_drop.c_str());
00209         const size_t side_index = side-1;
00210 
00211         bool restart = side == resources::screen->playing_side();
00212 
00213         if (side_index >= resources::teams->size()) {
00214             ERR_NW << "unknown side " << side_index << " is dropping game\n";
00215             throw network::error("");
00216         }
00217 
00218         team &tm = (*resources::teams)[side_index];
00219         unit_map::iterator leader = resources::units->find_leader(side);
00220         const bool have_leader = leader.valid();
00221 
00222         if (controller == "ai"){
00223             tm.make_ai();
00224             tm.set_current_player("ai" + side_drop);
00225             if (have_leader) leader->rename("ai" + side_drop);
00226             return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
00227         }
00228 
00229         int action = 0;
00230 
00231         std::vector<std::string> observers;
00232         std::vector<team*> allies;
00233         std::vector<std::string> options;
00234 
00235         // We want to give host chance to decide what to do for side
00236         {
00237             utils::string_map t_vars;
00238             options.push_back(_("Replace with AI"));
00239             options.push_back(_("Replace with local player"));
00240             options.push_back(_("Abort game"));
00241 
00242             //get all observers in as options to transfer control
00243             foreach (const std::string &ob, resources::screen->observers())
00244             {
00245                 t_vars["player"] = ob;
00246                 options.push_back(vgettext("Replace with $player", t_vars));
00247                 observers.push_back(ob);
00248             }
00249 
00250             //get all allies in as options to transfer control
00251             foreach (team &t, *resources::teams)
00252             {
00253                 if (!t.is_enemy(side) && !t.is_human() && !t.is_ai() && !t.is_empty()
00254                     && t.current_player() != tm.current_player())
00255                 {
00256                     //if this is an ally of the dropping side and it is not us (choose local player
00257                     //if you want that) and not ai or empty and if it is not the dropping side itself,
00258                     //get this team in as well
00259                     t_vars["player"] = t.current_player();
00260                     options.push_back(vgettext("Replace with $player", t_vars));
00261                     allies.push_back(&t);
00262                 }
00263             }
00264 
00265             t_vars["player"] = tm.current_player();
00266             const std::string msg =  vgettext("$player has left the game. What do you want to do?", t_vars);
00267             gui2::tsimple_item_selector dlg("", msg, options);
00268             dlg.set_single_button(true);
00269             dlg.show(resources::screen->video());
00270             action = dlg.selected_index();
00271         }
00272 
00273         //make the player an AI, and redo this turn, in case
00274         //it was the current player's team who has just changed into
00275         //an AI.
00276         switch(action) {
00277             case 0:
00278                 tm.make_human_ai();
00279                 tm.set_current_player("ai" + side_drop);
00280                 if (have_leader) leader->rename("ai" + side_drop);
00281                 change_controller(side_drop, "human_ai");
00282                 resources::controller->maybe_do_init_side(side_index);
00283 
00284                 return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
00285 
00286             case 1:
00287                 tm.make_human();
00288                 tm.set_current_player("human" + side_drop);
00289                 if (have_leader) leader->rename("human" + side_drop);
00290 
00291                 resources::controller->maybe_do_init_side(side_index);
00292 
00293                 return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
00294             case 2:
00295                 //The user pressed "end game". Don't throw a network error here or he will get
00296                 //thrown back to the title screen.
00297                 throw end_level_exception(QUIT);
00298             default:
00299                 if (action > 2) {
00300 
00301                     {
00302                         // Server thinks this side is ours now so in case of error transferring side we have to make local state to same as what server thinks it is.
00303                         tm.make_human();
00304                         tm.set_current_player("human"+side_drop);
00305                         if (have_leader) leader->rename("human"+side_drop);
00306                     }
00307 
00308                     const size_t index = static_cast<size_t>(action - 3);
00309                     if (index < observers.size()) {
00310                         change_side_controller(side_drop, observers[index]);
00311                     } else if (index < options.size() - 1) {
00312                         size_t i = index - observers.size();
00313                         change_side_controller(side_drop, allies[i]->current_player());
00314                     } else {
00315                         tm.make_human_ai();
00316                         tm.set_current_player("ai"+side_drop);
00317                         if (have_leader) leader->rename("ai" + side_drop);
00318                         change_controller(side_drop, "human_ai");
00319                     }
00320                     return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
00321                 }
00322                 break;
00323         }
00324         throw network::error("");
00325     }
00326 
00327     // The host has ended linger mode in a campaign -> enable the "End scenario" button
00328     // and tell we did get the notification.
00329     if (cfg.child("notify_next_scenario")) {
00330         gui::button* btn_end = resources::screen->find_button("button-endturn");
00331         if(btn_end) {
00332             btn_end->enable(true);
00333         }
00334         return PROCESS_END_LINGER;
00335     }
00336 
00337     //If this client becomes the new host, notify the play_controller object about it
00338     if (const config &cfg_host_transfer = cfg.child("host_transfer")){
00339         if (cfg_host_transfer["value"] == "1") {
00340             host_transfer_.notify_observers();
00341         }
00342     }
00343 
00344     return turn_end ? PROCESS_END_TURN : PROCESS_CONTINUE;
00345 }
00346 
00347 void turn_info::change_controller(const std::string& side, const std::string& controller)
00348 {
00349     config cfg;
00350     config& change = cfg.add_child("change_controller");
00351     change["side"] = side;
00352     change["controller"] = controller;
00353 
00354     network::send_data(cfg, 0);
00355 }
00356 
00357 
00358 void turn_info::change_side_controller(const std::string& side, const std::string& player)
00359 {
00360     config cfg;
00361     config& change = cfg.add_child("change_controller");
00362     change["side"] = side;
00363     change["player"] = player;
00364     network::send_data(cfg, 0);
00365 }
00366 
00367 #if 0
00368 void turn_info::take_side(const std::string& side, const std::string& controller)
00369 {
00370     config cfg;
00371     cfg["side"] = side;
00372     cfg["controller"] = controller;
00373     cfg["name"] = controller+side;
00374     network::send_data(cfg, 0, true);
00375 }
00376 #endif
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:08 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs