The Battle for Wesnoth  1.15.0+dev
playturn.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://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 
17 #include "actions/undo.hpp" // for undo_list
18 #include "chat_events.hpp" // for chat_handler, etc
19 #include "config.hpp" // for config, etc
20 #include "display_chat_manager.hpp" // for add_chat_message, add_observer, etc
21 #include "formula/string_utils.hpp" // for VGETTEXT
22 #include "game_board.hpp" // for game_board
23 #include "game_display.hpp" // for game_display
24 #include "game_end_exceptions.hpp" // for end_level_exception, etc
25 #include "gettext.hpp" // for _
27 #include "log.hpp" // for LOG_STREAM, logger, etc
28 #include "utils/make_enum.hpp" // for bad_enum_cast
29 #include "map/label.hpp"
30 #include "play_controller.hpp" // for play_controller
31 #include "playturn_network_adapter.hpp" // for playturn_network_adapter
32 #include "preferences/general.hpp" // for message_bell
33 #include "replay.hpp" // for replay, recorder, do_replay, etc
34 #include "resources.hpp" // for gameboard, screen, etc
35 #include "serialization/string_utils.hpp" // for string_map
36 #include "synced_context.hpp"
37 #include "team.hpp" // for team, team::CONTROLLER::AI, etc
39 #include "whiteboard/manager.hpp" // for manager
40 #include "widgets/button.hpp" // for button
41 
42 #include <cassert> // for assert
43 #include <ctime> // for time
44 #include <ostream> // for operator<<, basic_ostream, etc
45 #include <vector> // for vector
46 
47 static lg::log_domain log_network("network");
48 #define ERR_NW LOG_STREAM(err, log_network)
49 
51  replay_sender_(replay_sender),
52  host_transfer_("host_transfer"),
53  network_reader_(network_reader)
54 {
55 }
56 
58 {
59 }
60 
62 {
63  //there should be nothing left on the replay and we should get turn_info::PROCESS_CONTINUE back.
65  if(resources::controller->is_networked_mp()) {
66 
67  //receive data first, and then send data. When we sent the end of
68  //the AI's turn, we don't want there to be any chance where we
69  //could get data back pertaining to the next turn.
70  config cfg;
71  while( (retv == turn_info::PROCESS_CONTINUE) && network_reader_.read(cfg)) {
72  retv = process_network_data(cfg);
73  cfg.clear();
74  }
75  send_data();
76  }
77  return retv;
78 }
79 
81 {
83  if ( !send_everything ) {
85  } else {
87  }
88 }
89 
91 {
92  //t can contain a [command] or a [upload_log]
93  assert(t.all_children_count() == 1);
94 
95  if(!t.child_or_empty("command").has_child("speak") && chat_only) {
96  return PROCESS_CANNOT_HANDLE;
97  }
98  /** @todo FIXME: Check what commands we execute when it's our turn! */
99 
100  //note, that this function might call itself recursively: do_replay -> ... -> get_user_choice -> ... -> playmp_controller::pull_remote_choice -> sync_network -> handle_turn
103  return retv;
104 }
105 
107 {
108  if (resources::controller != nullptr) {
110  }
111 }
112 
114 {
115  config cfg;
116  while(this->network_reader_.read(cfg))
117  {
119  if(res != PROCESS_CONTINUE)
120  {
121  return res;
122  }
123  cfg.clear();
124  }
125  return PROCESS_CONTINUE;
126 }
127 
129 {
130  // the simple wesnothserver implementation in wesnoth was removed years ago.
131  assert(cfg.all_children_count() == 1);
132  assert(cfg.attribute_range().empty());
133  if(!resources::recorder->at_end())
134  {
135  ERR_NW << "processing network data while still having data on the replay." << std::endl;
136  }
137 
138  if (const config &message = cfg.child("message"))
139  {
140  game_display::get_singleton()->get_chat_manager().add_chat_message(std::time(nullptr), message["sender"], message["side"],
141  message["message"], events::chat_handler::MESSAGE_PUBLIC,
143  }
144  else if (const config &whisper = cfg.child("whisper") /*&& is_observer()*/)
145  {
146  game_display::get_singleton()->get_chat_manager().add_chat_message(std::time(nullptr), "whisper: " + whisper["sender"].str(), 0,
147  whisper["message"], events::chat_handler::MESSAGE_PRIVATE,
149  }
150  else if (const config &observer = cfg.child("observer") )
151  {
153  }
154  else if (const config &observer_quit = cfg.child("observer_quit"))
155  {
157  }
158  else if (cfg.child("leave_game")) {
159  throw ingame_wesnothd_error("");
160  }
161  else if (const config &turn = cfg.child("turn"))
162  {
163  return handle_turn(turn, chat_only);
164  }
165  else if (cfg.has_child("whiteboard"))
166  {
167  set_scontext_unsynced scontext;
168  resources::whiteboard->process_network_data(cfg);
169  }
170  else if (const config &change = cfg.child("change_controller"))
171  {
172  if(change.empty()) {
173  ERR_NW << "Bad [change_controller] signal from server, [change_controller] tag was empty." << std::endl;
174  return PROCESS_CONTINUE;
175  }
176 
177  const int side = change["side"].to_int();
178  const bool is_local = change["is_local"].to_bool();
179  const std::string player = change["player"];
180  const std::size_t index = side - 1;
181  if(index >= resources::gameboard->teams().size()) {
182  ERR_NW << "Bad [change_controller] signal from server, side out of bounds: " << change.debug() << std::endl;
183  return PROCESS_CONTINUE;
184  }
185 
186  const team & tm = resources::gameboard->teams().at(index);
187  const bool was_local = tm.is_local();
188 
189  resources::gameboard->side_change_controller(side, is_local, player);
190 
191  if (!was_local && tm.is_local()) {
193  }
194 
195  auto disp_set_team = [](int side_index) {
196  const bool side_changed = static_cast<int>(display::get_singleton()->viewing_team()) != side_index;
197  display::get_singleton()->set_team(side_index);
198 
199  if(side_changed) {
203  }
204  };
205 
206  if (resources::gameboard->is_observer() || (resources::gameboard->teams())[display::get_singleton()->playing_team()].is_local_human()) {
207  disp_set_team(display::get_singleton()->playing_team());
208  } else if (tm.is_local_human()) {
209  disp_set_team(side - 1);
210  }
211 
212  resources::whiteboard->on_change_controller(side,tm);
213 
215 
216  const bool restart = game_display::get_singleton()->playing_side() == side && (was_local || tm.is_local());
217  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
218  }
219 
220  else if (const config &side_drop_c = cfg.child("side_drop"))
221  {
222  const int side_drop = side_drop_c["side_num"].to_int(0);
223  std::size_t index = side_drop -1;
224 
225  bool restart = side_drop == game_display::get_singleton()->playing_side();
226 
227  if (index >= resources::gameboard->teams().size()) {
228  ERR_NW << "unknown side " << side_drop << " is dropping game" << std::endl;
229  throw ingame_wesnothd_error("");
230  }
231 
232  team::CONTROLLER ctrl;
233  if(!ctrl.parse(side_drop_c["controller"])) {
234  ERR_NW << "unknown controller type issued from server on side drop: " << side_drop_c["controller"] << std::endl;
235  throw ingame_wesnothd_error("");
236  }
237 
238  if (ctrl == team::CONTROLLER::AI) {
239  resources::gameboard->side_drop_to(side_drop, ctrl);
240  return restart ? PROCESS_RESTART_TURN:PROCESS_CONTINUE;
241  }
242  //null controlled side cannot be dropped because they aren't controlled by anyone.
243  else if (ctrl != team::CONTROLLER::HUMAN) {
244  ERR_NW << "unknown controller type issued from server on side drop: " << ctrl.to_cstring() << std::endl;
245  throw ingame_wesnothd_error("");
246  }
247 
248  int action = 0;
249  int first_observer_option_idx = 0;
250  int control_change_options = 0;
251  bool has_next_scenario = !resources::gamedata->next_scenario().empty() && resources::gamedata->next_scenario() != "null";
252 
253  std::vector<std::string> observers;
254  std::vector<const team *> allies;
255  std::vector<std::string> options;
256 
257  const team &tm = resources::gameboard->teams()[index];
258 
259  for (const team &t : resources::gameboard->teams()) {
260  if (!t.is_enemy(side_drop) && !t.is_local_human() && !t.is_local_ai() && !t.is_network_ai() && !t.is_empty()
261  && t.current_player() != tm.current_player()) {
262  allies.push_back(&t);
263  }
264  }
265 
266  // We want to give host chance to decide what to do for side
267  if (!resources::controller->is_linger_mode() || has_next_scenario) {
268  utils::string_map t_vars;
269 
270  //get all allies in as options to transfer control
271  for (const team *t : allies) {
272  //if this is an ally of the dropping side and it is not us (choose local player
273  //if you want that) and not ai or empty and if it is not the dropping side itself,
274  //get this team in as well
275  t_vars["player"] = t->current_player();
276  options.emplace_back(VGETTEXT("Give control to their ally $player", t_vars));
277  control_change_options++;
278  }
279 
280  first_observer_option_idx = options.size();
281 
282  //get all observers in as options to transfer control
283  for (const std::string &screen_observers : game_display::get_singleton()->observers()) {
284  t_vars["player"] = screen_observers;
285  options.emplace_back(VGETTEXT("Give control to observer $player", t_vars));
286  observers.push_back(screen_observers);
287  control_change_options++;
288  }
289 
290  options.emplace_back(_("Replace with AI"));
291  options.emplace_back(_("Replace with local player"));
292  options.emplace_back(_("Set side to idle"));
293  options.emplace_back(_("Save and abort game"));
294 
295  t_vars["player"] = tm.current_player();
296  t_vars["side_drop"] = std::to_string(side_drop);
297  const std::string gettext_message = VGETTEXT("$player who controlled side $side_drop has left the game. What do you want to do?", t_vars);
298  gui2::dialogs::simple_item_selector dlg("", gettext_message, options);
299  dlg.set_single_button(true);
300  dlg.show();
301  action = dlg.selected_index();
302 
303  // If esc was pressed, default to setting side to idle
304  if (action == -1) {
305  action = control_change_options + 2;
306  }
307  } else {
308  // Always set leaving side to idle if in linger mode and there is no next scenario
309  action = 2;
310  }
311 
312  if (action < control_change_options) {
313  // Grant control to selected ally
314 
315  {
316  // 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.
317  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_IDLE);
318  }
319 
320  if (action < first_observer_option_idx) {
321  change_side_controller(side_drop, allies[action]->current_player());
322  } else {
323  change_side_controller(side_drop, observers[action - first_observer_option_idx]);
324  }
325 
326  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
327  } else {
328  action -= control_change_options;
329 
330  //make the player an AI, and redo this turn, in case
331  //it was the current player's team who has just changed into
332  //an AI.
333  switch(action) {
334  case 0:
336  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_AI);
337 
338  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
339 
340  case 1:
342  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_HUMAN);
343 
344  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
345  case 2:
346  resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_IDLE);
347 
348  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
349 
350  case 3:
351  //The user pressed "end game". Don't throw a network error here or he will get
352  //thrown back to the title screen.
353  do_save();
355  default:
356  break;
357  }
358  }
359  }
360 
361  // The host has ended linger mode in a campaign -> enable the "End scenario" button
362  // and tell we did get the notification.
363  else if (cfg.child("notify_next_scenario")) {
364  if(chat_only) {
365  return PROCESS_CANNOT_HANDLE;
366  }
367  std::shared_ptr<gui::button> btn_end = display::get_singleton()->find_action_button("button-endturn");
368  if(btn_end) {
369  btn_end->enable(true);
370  }
371  return PROCESS_END_LINGER;
372  }
373 
374  //If this client becomes the new host, notify the play_controller object about it
375  else if (cfg.child("host_transfer")){
377  }
378  else
379  {
380  ERR_NW << "found unknown command:\n" << cfg.debug() << std::endl;
381  }
382 
383  return PROCESS_CONTINUE;
384 }
385 
386 
387 void turn_info::change_side_controller(int side, const std::string& player)
388 {
389  config cfg;
390  config& change = cfg.add_child("change_controller");
391  change["side"] = side;
392  change["player"] = player;
394 }
395 
397 {
398  switch(replayreturn)
399  {
401  return PROCESS_CONTINUE;
405  return PROCESS_END_TURN;
407  return PROCESS_END_LEVEL;
408  default:
409  assert(false);
410  throw "found invalid REPLAY_RETURN";
411  }
412 }
std::shared_ptr< gui::button > find_action_button(const std::string &id)
Retrieves a pointer to a theme UI button.
Definition: display.cpp:803
play_controller * controller
Definition: resources.cpp:21
static void change_side_controller(int side, const std::string &player)
Definition: playturn.cpp:387
config & child(config_key_type 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:420
replay_network_sender & replay_sender_
Definition: playturn.hpp:70
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:88
~turn_info()
Definition: playturn.cpp:57
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:92
std::map< std::string, t_string > string_map
virtual void notify_observers()
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:412
events::generic_event host_transfer_
Definition: playturn.hpp:72
static bool is_unsynced()
void redraw_everything()
Invalidates entire screen, including all tiles and sidebar.
Definition: display.cpp:2375
void sync_non_undoable()
Definition: replay.cpp:906
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:61
bool message_bell()
Definition: general.cpp:653
Replay control code.
void clear()
Definition: config.cpp:863
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:677
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:113
an object to leave the synced context during draw or unsynced wml items when we don’t know whether w...
virtual int playing_side() const override
The playing team is the team whose turn it is.
-file sdl_utils.hpp
bool show(const unsigned auto_close_time=0)
Shows the window.
#define ERR_NW
Definition: playturn.cpp:48
Definitions for the interface to Wesnoth Markup Language (WML).
turn_info(replay_network_sender &network_sender, playturn_network_adapter &network_reader)
Definition: playturn.cpp:50
void send_data()
Definition: playturn.cpp:80
const_attr_itors attribute_range() const
Definition: config.cpp:809
game_data * gamedata
Definition: resources.cpp:22
void add_chat_message(const std::time_t &time, const std::string &speaker, int side, const std::string &msg, events::chat_handler::MESSAGE_TYPE type, bool bell)
PROCESS_DATA_RESULT
Definition: playturn.hpp:36
REPLAY_RETURN
Definition: replay.hpp:153
-file pathfind.hpp
const config & options()
Definition: game.cpp:582
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
void throw_quit_game_exception()
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
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:195
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.hpp:616
unsigned all_children_count() const
Definition: config.cpp:402
game_board * gameboard
Definition: resources.cpp:20
When we couldn&#39;t process the network data because we found a dependent command, this should only happ...
Definition: playturn.hpp:44
static lg::log_domain log_network("network")
replay * recorder
Definition: resources.cpp:28
playturn_network_adapter & network_reader_
Definition: playturn.hpp:74
void remove_observer(const std::string &name)
static bool is_simultaneously()
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
void recalculate_labels()
Definition: label.cpp:244
When the host uploaded the next scenario this is returned.
Definition: playturn.hpp:42
int selected_index() const
Returns the selected item index after displaying.
We found a player action in the replay that caused the game to end.
Definition: playturn.hpp:48
const std::string & current_player() const
Definition: team.hpp:234
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
const std::string & next_scenario() const
Definition: game_data.hpp:93
std::string observer
bool is_local() const
Definition: team.hpp:260
config & add_child(config_key_type key)
Definition: config.cpp:476
void set_team(std::size_t team, bool observe=false)
Sets the team controlled by the player using the computer.
Definition: display.cpp:373
display_chat_manager & get_chat_manager()
bool is_local_human() const
Definition: team.hpp:266
double t
Definition: astarsearch.cpp:64
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:642
void add_observer(const std::string &name)
void trigger_full_redraw()
Definition: video.cpp:62
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:102
void do_save()
Definition: playturn.cpp:106
virtual void on_not_observer()=0
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:396
map_labels & labels()
Definition: display.cpp:2494
actions::undo_list * undo_stack
Definition: resources.cpp:32
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:453
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
PROCESS_DATA_RESULT process_network_data(const config &cfg, bool chat_only=false)
Definition: playturn.cpp:128
bool can_undo() const
True if there are actions that can be undone.
Definition: undo.hpp:93
Defines the MAKE_ENUM macro.
PROCESS_DATA_RESULT handle_turn(const config &t, bool chat_only=false)
Definition: playturn.cpp:90
when we couldn&#39;t handle the given action currently.
Definition: playturn.hpp:46
virtual void send_to_wesnothd(const config &, const std::string &="unknown") const
std::string debug() const
Definition: config.cpp:1277
static game_display * get_singleton()
void side_change_controller(int side_num, bool is_local, const std::string &pname="")
Definition: game_board.cpp:208
void set_single_button(bool value)
Sets whether the Cancel button should be hidden or not.