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