The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
playturn.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2016 by David White <[email protected]>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "playturn.hpp"
16 #include "global.hpp"
17 
18 #include "actions/undo.hpp" // for undo_list
19 #include "chat_events.hpp" // for chat_handler, etc
20 #include "config.hpp" // for config, etc
21 #include "display_chat_manager.hpp" // for add_chat_message, add_observer, etc
22 #include "formula/string_utils.hpp" // for vgettext
23 #include "game_board.hpp" // for game_board
24 #include "game_display.hpp" // for game_display
25 #include "game_end_exceptions.hpp" // for end_level_exception, etc
26 #include "gettext.hpp" // for _
28 #include "log.hpp" // for LOG_STREAM, logger, etc
29 #include "utils/make_enum.hpp" // for bad_enum_cast
30 #include "map/label.hpp"
31 #include "play_controller.hpp" // for play_controller
32 #include "playturn_network_adapter.hpp" // for playturn_network_adapter
33 #include "preferences.hpp" // for message_bell
34 #include "replay.hpp" // for replay, recorder, do_replay, etc
35 #include "resources.hpp" // for gameboard, screen, etc
36 #include "serialization/string_utils.hpp" // for string_map
37 #include "team.hpp" // for team, team::CONTROLLER::AI, etc
38 #include "tstring.hpp" // for operator==
39 #include "util.hpp" // for lexical_cast
41 #include "whiteboard/manager.hpp" // for manager
42 #include "widgets/button.hpp" // for button
43 
44 #include <boost/shared_ptr.hpp> // for shared_ptr
45 #include <cassert> // for assert
46 #include <ctime> // for time
47 #include <ostream> // for operator<<, basic_ostream, etc
48 #include <vector> // for vector
49 
50 static lg::log_domain log_network("network");
51 #define ERR_NW LOG_STREAM(err, log_network)
52 
54  replay_sender_(replay_sender),
55  host_transfer_("host_transfer"),
56  network_reader_(network_reader)
57 {
58 }
59 
61 {
62 }
63 
65 {
66  //there should be nothing left on the replay and we should get turn_info::PROCESS_CONTINUE back.
68  if(resources::controller->is_networked_mp()) {
69 
70  //receive data first, and then send data. When we sent the end of
71  //the AI's turn, we don't want there to be any chance where we
72  //could get data back pertaining to the next turn.
73  config cfg;
74  while( (retv == turn_info::PROCESS_CONTINUE) && network_reader_.read(cfg)) {
75  retv = process_network_data(cfg);
76  cfg.clear();
77  }
78  send_data();
79  }
80  return retv;
81 }
82 
84 {
85  if ( resources::undo_stack->can_undo() ) {
87  } else {
89  }
90 }
91 
93 {
94  //t can contain a [command] or a [upload_log]
95  assert(t.all_children_count() == 1);
96  /** @todo FIXME: Check what commands we execute when it's our turn! */
97 
98  //note, that this function might call itself recursively: do_replay -> ... -> get_user_choice -> ... -> playmp_controller::pull_remote_choice -> sync_network -> handle_turn
101  return retv;
102 }
103 
105 {
106  if (resources::controller != nullptr) {
108  }
109 }
110 
112 {
113  config cfg;
114  while(this->network_reader_.read(cfg))
115  {
117  if(res != PROCESS_CONTINUE)
118  {
119  return res;
120  }
121  cfg.clear();
122  }
123  return PROCESS_CONTINUE;
124 }
125 
127 {
128  // the simple wesnothserver implementation in wesnoth was removed years ago.
129  assert(cfg.all_children_count() == 1);
130  assert(cfg.attribute_range().first == cfg.attribute_range().second);
131  if(!resources::recorder->at_end())
132  {
133  ERR_NW << "processing network data while still having data on the replay." << std::endl;
134  }
135 
136  if (const config &msg = cfg.child("message"))
137  {
138  resources::screen->get_chat_manager().add_chat_message(time(nullptr), msg["sender"], msg["side"],
141  }
142  else if (const config &msg = cfg.child("whisper") /*&& is_observer()*/)
143  {
144  resources::screen->get_chat_manager().add_chat_message(time(nullptr), "whisper: " + msg["sender"].str(), 0,
147  }
148  else if (const config &ob = cfg.child("observer") )
149  {
151  }
152  else if (const config &ob = cfg.child("observer_quit"))
153  {
155  }
156  else if (cfg.child("leave_game")) {
157  throw ingame_wesnothd_error("");
158  }
159  else if (const config &turn = cfg.child("turn"))
160  {
161  return handle_turn(turn);
162  }
163  else if (cfg.has_child("whiteboard"))
164  {
165  resources::whiteboard->process_network_data(cfg);
166  }
167  else if (const config &change = cfg.child("change_controller"))
168  {
169  if(change.empty()) {
170  ERR_NW << "Bad [change_controller] signal from server, [change_controller] tag was empty." << std::endl;
171  return PROCESS_CONTINUE;
172  }
173 
174  const int side = change["side"].to_int();
175  const bool is_local = change["is_local"].to_bool();
176  const std::string player = change["player"];
177  const size_t index = side - 1;
178  if(index >= resources::gameboard->teams().size()) {
179  ERR_NW << "Bad [change_controller] signal from server, side out of bounds: " << change.debug() << std::endl;
180  return PROCESS_CONTINUE;
181  }
182 
183  const team & tm = resources::gameboard->teams().at(index);
184  const bool was_local = tm.is_local();
185 
186  resources::gameboard->side_change_controller(side, is_local, player);
187 
188  if (!was_local && tm.is_local()) {
190  }
191 
192  if (resources::gameboard->is_observer() || (resources::gameboard->teams())[resources::screen->playing_team()].is_local_human()) {
193  resources::screen->set_team(resources::screen->playing_team());
196  } else if (tm.is_local_human()) {
197  resources::screen->set_team(side - 1);
200  }
201 
202  resources::whiteboard->on_change_controller(side,tm);
203 
205 
206  const bool restart = resources::screen->playing_side() == side && (was_local || tm.is_local());
207  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
208  }
209 
210  else if (const config &side_drop_c = cfg.child("side_drop"))
211  {
212  const int side_drop = side_drop_c["side_num"].to_int(0);
213  size_t index = side_drop -1;
214 
215  bool restart = side_drop == resources::screen->playing_side();
216 
217  if (index >= resources::teams->size()) {
218  ERR_NW << "unknown side " << side_drop << " is dropping game" << std::endl;
219  throw ingame_wesnothd_error("");
220  }
221 
222  team::CONTROLLER ctrl;
223  if(!ctrl.parse(side_drop_c["controller"])) {
224  ERR_NW << "unknown controller type issued from server on side drop: " << side_drop_c["controller"] << std::endl;
225  throw ingame_wesnothd_error("");
226  }
227 
228  if (ctrl == team::CONTROLLER::AI) {
229  resources::gameboard->side_drop_to(side_drop, ctrl);
230  return restart ? PROCESS_RESTART_TURN:PROCESS_CONTINUE;
231  }
232  //null controlled side cannot be dropped becasue they aren't controlled by anyone.
233  else if (ctrl != team::CONTROLLER::HUMAN) {
234  ERR_NW << "unknown controller type issued from server on side drop: " << ctrl.to_cstring() << std::endl;
235  throw ingame_wesnothd_error("");
236  }
237 
238  int action = 0;
239  int first_observer_option_idx = 0;
240  int control_change_options = 0;
241  bool has_next_scenario = !resources::gamedata->next_scenario().empty() && resources::gamedata->next_scenario() != "null";
242 
243  std::vector<std::string> observers;
244  std::vector<const team *> allies;
245  std::vector<std::string> options;
246 
247  const team &tm = resources::gameboard->teams()[index];
248 
249  for (const team &t : resources::gameboard->teams()) {
250  if (!t.is_enemy(side_drop) && !t.is_local_human() && !t.is_local_ai() && !t.is_network_ai() && !t.is_empty()
251  && t.current_player() != tm.current_player()) {
252  allies.push_back(&t);
253  }
254  }
255 
256  // We want to give host chance to decide what to do for side
257  if (!resources::controller->is_linger_mode() || has_next_scenario) {
258  utils::string_map t_vars;
259 
260  //get all allies in as options to transfer control
261  for (const team *t : allies) {
262  //if this is an ally of the dropping side and it is not us (choose local player
263  //if you want that) and not ai or empty and if it is not the dropping side itself,
264  //get this team in as well
265  t_vars["player"] = t->current_player();
266  options.push_back(vgettext("Give control to their ally $player", t_vars));
267  control_change_options++;
268  }
269 
270  first_observer_option_idx = options.size();
271 
272  //get all observers in as options to transfer control
273  for (const std::string &ob : resources::screen->observers()) {
274  t_vars["player"] = ob;
275  options.push_back(vgettext("Give control to observer $player", t_vars));
276  observers.push_back(ob);
277  control_change_options++;
278  }
279 
280  options.push_back(_("Replace with AI"));
281  options.push_back(_("Replace with local player"));
282  options.push_back(_("Set side to idle"));
283  options.push_back(_("Save and abort game"));
284 
285  t_vars["player"] = tm.current_player();
286  const std::string msg = vgettext("$player has left the game. What do you want to do?", t_vars);
287  gui2::tsimple_item_selector dlg("", msg, options);
288  dlg.set_single_button(true);
289  dlg.show(resources::screen->video());
290  action = dlg.selected_index();
291 
292  // If esc was pressed, default to setting side to idle
293  if (action == -1) {
294  action = control_change_options + 2;
295  }
296  } else {
297  // Always set leaving side to idle if in linger mode and there is no next scenario
298  action = 2;
299  }
300 
301  if (action < control_change_options) {
302  // Grant control to selected ally
303 
304  {
305  // 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.
306  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_IDLE);
307  }
308 
309  if (action < first_observer_option_idx) {
310  change_side_controller(side_drop, allies[action]->current_player());
311  } else {
312  change_side_controller(side_drop, observers[action - first_observer_option_idx]);
313  }
314 
315  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
316  } else {
317  action -= control_change_options;
318 
319  //make the player an AI, and redo this turn, in case
320  //it was the current player's team who has just changed into
321  //an AI.
322  switch(action) {
323  case 0:
325  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_AI);
326 
327  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
328 
329  case 1:
331  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_HUMAN);
332 
333  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
334  case 2:
335  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_IDLE);
336 
337  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
338 
339  case 3:
340  //The user pressed "end game". Don't throw a network error here or he will get
341  //thrown back to the title screen.
342  do_save();
344  default:
345  break;
346  }
347  }
348  }
349 
350  // The host has ended linger mode in a campaign -> enable the "End scenario" button
351  // and tell we did get the notification.
352  else if (cfg.child("notify_next_scenario")) {
353  std::shared_ptr<gui::button> btn_end = resources::screen->find_action_button("button-endturn");
354  if(btn_end) {
355  btn_end->enable(true);
356  }
357  return PROCESS_END_LINGER;
358  }
359 
360  //If this client becomes the new host, notify the play_controller object about it
361  else if (cfg.child("host_transfer")){
363  }
364  else
365  {
366  ERR_NW << "found unknown command:\n" << cfg.debug() << std::endl;
367  }
368 
369  return PROCESS_CONTINUE;
370 }
371 
372 
373 void turn_info::change_side_controller(int side, const std::string& player)
374 {
375  config cfg;
376  config& change = cfg.add_child("change_controller");
377  change["side"] = side;
378  change["player"] = player;
380 }
381 
383 {
384  switch(replayreturn)
385  {
387  return PROCESS_CONTINUE;
391  return PROCESS_END_TURN;
393  return PROCESS_END_LEVEL;
394  default:
395  assert(false);
396  throw "found invalid REPLAY_RETURN";
397  }
398 }
std::shared_ptr< gui::button > find_action_button(const std::string &id)
Retrieves a pointer to a theme UI button.
Definition: display.cpp:826
play_controller * controller
Definition: resources.cpp:21
static void change_side_controller(int side, const std::string &player)
Definition: playturn.cpp:373
bool is_local() const
Definition: team.hpp:261
virtual void send_to_wesnothd(const config &, const std::string &="unknown") const
replay_network_sender & replay_sender_
Definition: playturn.hpp:69
const std::string & current_player() const
Definition: team.hpp:238
~turn_info()
Definition: playturn.cpp:60
virtual void notify_observers()
bool show(CVideo &video, const unsigned auto_close_time=0)
Shows the window.
Definition: dialog.cpp:34
game_display * screen
Definition: resources.cpp:27
events::generic_event host_transfer_
Definition: playturn.hpp:71
void redraw_everything()
Invalidates entire screen, including all tiles and sidebar.
Definition: display.cpp:2650
void sync_non_undoable()
Definition: replay.cpp:883
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:64
bool message_bell()
virtual const std::vector< team > & teams() const
Definition: game_board.hpp:97
Replay control code.
PROCESS_DATA_RESULT handle_turn(const config &t)
Definition: playturn.cpp:92
void clear()
Definition: config.cpp:1055
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:659
Contains the exception interfaces used to signal completion of a scenario, campaign or turn...
PROCESS_DATA_RESULT process_network_data_from_reader()
Definition: playturn.cpp:111
std::string debug() const
Definition: config.cpp:1438
-file sdl_utils.hpp
#define ERR_NW
Definition: playturn.cpp:51
GLdouble GLdouble t
Definition: glew.h:1366
Definitions for the interface to Wesnoth Markup Language (WML).
turn_info(replay_network_sender &network_sender, playturn_network_adapter &network_reader)
Definition: playturn.cpp:53
void send_data()
Definition: playturn.cpp:83
game_data * gamedata
Definition: resources.cpp:22
PROCESS_DATA_RESULT
Definition: playturn.hpp:37
void set_single_button(bool value)
Sets whether the Cancel button should be hidden or not.
REPLAY_RETURN
Definition: replay.hpp:155
-file pathfind.hpp
bool has_child(const std::string &key) const
Determine whether a config has a child or not.
Definition: config.cpp:651
static std::vector< team > *& teams
Definition: team.cpp:50
const config & options()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:50
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
void throw_quit_game_exception()
std::map< std::string, t_string > string_map
std::vector< team > * teams
Definition: resources.cpp:29
We received invalid data from wesnothd during a game This means we cannot continue with the game but ...
void side_drop_to(int side_num, team::CONTROLLER ctrl, team::PROXY_CONTROLLER proxy=team::PROXY_CONTROLLER::PROXY_HUMAN)
Definition: game_board.cpp:200
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.hpp:623
game_board * gameboard
Definition: resources.cpp:20
int selected_index() const
Returns the selected item index after displaying.
config & add_child(const std::string &key)
Definition: config.cpp:743
When we couldn't process the network data because we found a dependent command, this should only happ...
Definition: playturn.hpp:45
static lg::log_domain log_network("network")
bool is_local_human() const
Definition: team.hpp:267
replay * recorder
Definition: resources.cpp:30
playturn_network_adapter & network_reader_
Definition: playturn.hpp:73
Templates and utility-routines for strings and numbers.
void remove_observer(const std::string &name)
void recalculate_labels()
Definition: label.cpp:255
GLuint res
Definition: glew.h:9258
PROCESS_DATA_RESULT process_network_data(const config &cfg)
Definition: playturn.cpp:126
When the host uploaded the next scenario this is returned.
Definition: playturn.hpp:43
We foudn a player action in the replay that caused the game to end.
Definition: playturn.hpp:47
int playing_side() const
The playing team is the team whose turn it is.
const std::string & next_scenario() const
Definition: game_data.hpp:89
const_attr_itors attribute_range() const
Definition: config.cpp:984
GLuint index
Definition: glew.h:1782
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:112
std::string vgettext(const char *msgid, const utils::string_map &symbols)
GLsizeiptr size
Definition: glew.h:1649
display_chat_manager & get_chat_manager()
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:627
void add_observer(const std::string &name)
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
void do_save()
Definition: playturn.cpp:104
virtual void on_not_observer()=0
unsigned all_children_count() const
Definition: config.cpp:646
Various functions that implement the undoing (and redoing) of in-game commands.
Standard logging facilities (interface).
static PROCESS_DATA_RESULT replay_to_process_data_result(REPLAY_RETURN replayreturn)
Definition: playturn.cpp:382
boost::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:36
map_labels & labels()
Definition: display.cpp:2773
actions::undo_list * undo_stack
Definition: resources.cpp:34
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
void add_chat_message(const time_t &time, const std::string &speaker, int side, const std::string &msg, events::chat_handler::MESSAGE_TYPE type, bool bell)
Defines the MAKE_ENUM macro.
GLsizei const GLcharARB ** string
Definition: glew.h:4503
void side_change_controller(int side_num, bool is_local, const std::string &pname="")
Definition: game_board.cpp:213
void set_team(size_t team, bool observe=false)
Sets the team controlled by the player using the computer.
Definition: display.cpp:379