The Battle for Wesnoth  1.17.4+dev
playturn.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
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 "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 auto message = cfg.optional_child("message"))
139  {
140  game_display::get_singleton()->get_chat_manager().add_chat_message(std::time(nullptr), message.value()["sender"], message.value()["side"],
141  message.value()["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  const bool has_reason = cfg.child("leave_game").has_attribute("reason");
160  throw leavegame_wesnothd_error(has_reason ? cfg.child("leave_game")["reason"].str() : "");
161  }
162  else if (const config &turn = cfg.child("turn"))
163  {
164  return handle_turn(turn, chat_only);
165  }
166  else if (cfg.has_child("whiteboard"))
167  {
168  set_scontext_unsynced scontext;
169  resources::whiteboard->process_network_data(cfg);
170  }
171  else if (const config &change = cfg.child("change_controller"))
172  {
173  if(change.empty()) {
174  ERR_NW << "Bad [change_controller] signal from server, [change_controller] tag was empty." << std::endl;
175  return PROCESS_CONTINUE;
176  }
177 
178  const int side = change["side"].to_int();
179  const bool is_local = change["is_local"].to_bool();
180  const std::string player = change["player"];
181  const std::string controller_type = change["controller"];
182  const std::size_t index = side - 1;
183  if(index >= resources::gameboard->teams().size()) {
184  ERR_NW << "Bad [change_controller] signal from server, side out of bounds: " << change.debug() << std::endl;
185  return PROCESS_CONTINUE;
186  }
187 
188  const team & tm = resources::gameboard->teams().at(index);
189  const bool was_local = tm.is_local();
190 
191  resources::gameboard->side_change_controller(side, is_local, player, controller_type);
192 
193  if (!was_local && tm.is_local()) {
195  }
196 
197  auto disp_set_team = [](int side_index) {
198  const bool side_changed = static_cast<int>(display::get_singleton()->viewing_team()) != side_index;
199  display::get_singleton()->set_team(side_index);
200 
201  if(side_changed) {
205  }
206  };
207 
208  if (resources::gameboard->is_observer() || (resources::gameboard->teams())[display::get_singleton()->playing_team()].is_local_human()) {
209  disp_set_team(display::get_singleton()->playing_team());
210  } else if (tm.is_local_human()) {
211  disp_set_team(side - 1);
212  }
213 
214  resources::whiteboard->on_change_controller(side,tm);
215 
217 
218  const bool restart = game_display::get_singleton()->playing_side() == side && (was_local || tm.is_local());
219  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
220  }
221 
222  else if (const config &side_drop_c = cfg.child("side_drop"))
223  {
224  const int side_drop = side_drop_c["side_num"].to_int(0);
225  std::size_t index = side_drop -1;
226 
227  bool restart = side_drop == game_display::get_singleton()->playing_side();
228 
229  if (index >= resources::gameboard->teams().size()) {
230  ERR_NW << "unknown side " << side_drop << " is dropping game" << std::endl;
231  throw ingame_wesnothd_error("");
232  }
233 
234  auto ctrl = side_controller::get_enum(side_drop_c["controller"].str());
235  if(!ctrl) {
236  ERR_NW << "unknown controller type issued from server on side drop: " << side_drop_c["controller"] << std::endl;
237  throw ingame_wesnothd_error("");
238  }
239 
240  if (ctrl == side_controller::type::ai) {
241  resources::gameboard->side_drop_to(side_drop, *ctrl);
242  return restart ? PROCESS_RESTART_TURN:PROCESS_CONTINUE;
243  }
244  //null controlled side cannot be dropped because they aren't controlled by anyone.
245  else if (ctrl != side_controller::type::human) {
246  ERR_NW << "unknown controller type issued from server on side drop: " << side_controller::get_string(*ctrl) << std::endl;
247  throw ingame_wesnothd_error("");
248  }
249 
250  int action = 0;
251  int first_observer_option_idx = 0;
252  int control_change_options = 0;
253  bool has_next_scenario = !resources::gamedata->next_scenario().empty() && resources::gamedata->next_scenario() != "null";
254 
255  std::vector<std::string> observers;
256  std::vector<const team *> allies;
257  std::vector<std::string> options;
258 
259  const team &tm = resources::gameboard->teams()[index];
260 
261  for (const team &t : resources::gameboard->teams()) {
262  if (!t.is_enemy(side_drop) && !t.is_local_human() && !t.is_local_ai() && !t.is_network_ai() && !t.is_empty()
263  && t.current_player() != tm.current_player()) {
264  allies.push_back(&t);
265  }
266  }
267 
268  // We want to give host chance to decide what to do for side
269  if (!resources::controller->is_linger_mode() || has_next_scenario) {
270  utils::string_map t_vars;
271 
272  //get all allies in as options to transfer control
273  for (const team *t : allies) {
274  //if this is an ally of the dropping side and it is not us (choose local player
275  //if you want that) and not ai or empty and if it is not the dropping side itself,
276  //get this team in as well
277  t_vars["player"] = t->current_player();
278  options.emplace_back(VGETTEXT("Give control to their ally $player", t_vars));
279  control_change_options++;
280  }
281 
282  first_observer_option_idx = options.size();
283 
284  //get all observers in as options to transfer control
285  for (const std::string &screen_observers : game_display::get_singleton()->observers()) {
286  t_vars["player"] = screen_observers;
287  options.emplace_back(VGETTEXT("Give control to observer $player", t_vars));
288  observers.push_back(screen_observers);
289  control_change_options++;
290  }
291 
292  options.emplace_back(_("Replace with AI"));
293  options.emplace_back(_("Replace with local player"));
294  options.emplace_back(_("Set side to idle"));
295  options.emplace_back(_("Save and abort game"));
296 
297  t_vars["player"] = tm.current_player();
298  t_vars["side_drop"] = std::to_string(side_drop);
299  const std::string gettext_message = VGETTEXT("$player who controlled side $side_drop has left the game. What do you want to do?", t_vars);
300  gui2::dialogs::simple_item_selector dlg("", gettext_message, options);
301  dlg.set_single_button(true);
302  dlg.show();
303  action = dlg.selected_index();
304 
305  // If esc was pressed, default to setting side to idle
306  if (action == -1) {
307  action = control_change_options + 2;
308  }
309  } else {
310  // Always set leaving side to idle if in linger mode and there is no next scenario
311  action = 2;
312  }
313 
314  if (action < control_change_options) {
315  // Grant control to selected ally
316 
317  {
318  // 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.
319  resources::gameboard->side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::idle);
320  }
321 
322  if (action < first_observer_option_idx) {
323  change_side_controller(side_drop, allies[action]->current_player());
324  } else {
325  change_side_controller(side_drop, observers[action - first_observer_option_idx]);
326  }
327 
328  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
329  } else {
330  action -= control_change_options;
331 
332  //make the player an AI, and redo this turn, in case
333  //it was the current player's team who has just changed into
334  //an AI.
335  switch(action) {
336  case 0:
338  resources::gameboard->side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::ai);
339 
340  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
341 
342  case 1:
344  resources::gameboard->side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::human);
345 
346  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
347  case 2:
348  resources::gameboard->side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::idle);
349 
350  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
351 
352  case 3:
353  //The user pressed "end game". Don't throw a network error here or he will get
354  //thrown back to the title screen.
355  do_save();
357  default:
358  break;
359  }
360  }
361  }
362 
363  // The host has ended linger mode in a campaign -> enable the "End scenario" button
364  // and tell we did get the notification.
365  else if (cfg.child("notify_next_scenario")) {
366  if(chat_only) {
367  return PROCESS_CANNOT_HANDLE;
368  }
369  std::shared_ptr<gui::button> btn_end = display::get_singleton()->find_action_button("button-endturn");
370  if(btn_end) {
371  btn_end->enable(true);
372  }
373  return PROCESS_END_LINGER;
374  }
375 
376  //If this client becomes the new host, notify the play_controller object about it
377  else if (cfg.child("host_transfer")){
379  }
380  else
381  {
382  ERR_NW << "found unknown command:\n" << cfg.debug() << std::endl;
383  }
384 
385  return PROCESS_CONTINUE;
386 }
387 
388 
389 void turn_info::change_side_controller(int side, const std::string& player)
390 {
391  config cfg;
392  config& change = cfg.add_child("change_controller");
393  change["side"] = side;
394  change["player"] = player;
396 }
397 
399 {
400  switch(replayreturn)
401  {
403  return PROCESS_CONTINUE;
407  return PROCESS_END_TURN;
409  return PROCESS_END_LEVEL;
410  default:
411  assert(false);
412  throw "found invalid REPLAY_RETURN";
413  }
414 }
std::shared_ptr< gui::button > find_action_button(const std::string &id)
Retrieves a pointer to a theme UI button.
Definition: display.cpp:821
play_controller * controller
Definition: resources.cpp:22
static void change_side_controller(int side, const std::string &player)
Definition: playturn.cpp:389
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:96
~turn_info()
Definition: playturn.cpp:57
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:86
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:2368
void sync_non_undoable()
Definition: replay.cpp:916
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:61
bool message_bell()
Definition: general.cpp:720
Replay control code.
void side_drop_to(int side_num, side_controller::type ctrl, side_proxy_controller::type proxy=side_proxy_controller::type::human)
Definition: game_board.cpp:221
void clear()
Definition: config.cpp:920
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:687
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.
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: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: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:562
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:75
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 recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.hpp:596
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
static std::string get_string(typename T::type key)
Uses the int value of the provided enum to get the associated index of the values array in the implem...
Definition: enum_base.hpp:41
const std::string & current_player() const
Definition: team.hpp:222
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:249
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:397
display_chat_manager & get_chat_manager()
bool is_local_human() const
Definition: team.hpp:255
static std::optional< typename T::type > get_enum(const std::string value)
Convert a string into its enum equivalent.
Definition: enum_base.hpp:52
double t
Definition: astarsearch.cpp:65
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:652
void add_observer(const std::string &name)
void trigger_full_redraw()
Definition: video.cpp:71
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:110
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:398
map_labels & labels()
Definition: display.cpp:2482
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:60
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:96
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: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.