The Battle for Wesnoth  1.17.12+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.";
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.";
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();
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) {
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";
229  throw ingame_wesnothd_error("");
230  }
231 
232  auto ctrl = side_controller::get_enum(side_drop_c["controller"].str());
233  if(!ctrl) {
234  ERR_NW << "unknown controller type issued from server on side drop: " << side_drop_c["controller"];
235  throw ingame_wesnothd_error("");
236  }
237 
238  if (ctrl == side_controller::type::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 != side_controller::type::human) {
244  ERR_NW << "unknown controller type issued from server on side drop: " << side_controller::get_string(*ctrl);
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, side_controller::type::human, side_proxy_controller::type::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, side_controller::type::human, side_proxy_controller::type::ai);
337 
338  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
339 
340  case 1:
342  resources::gameboard->side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::human);
343 
344  return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
345  case 2:
346  resources::gameboard->side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::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();
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:825
play_controller * controller
Definition: resources.cpp:22
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: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:98
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2322
~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 sync_non_undoable()
Definition: replay.cpp:915
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:61
bool message_bell()
Definition: general.cpp:723
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:222
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:556
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 side_change_controller(int side_num, bool is_local, const std::string &pname, const std::string &controller_type)
Definition: game_board.cpp:238
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.
static constexpr std::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
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: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:377
display_chat_manager & get_chat_manager()
bool is_local_human() const
Definition: team.hpp:255
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)
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:112
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:2619
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.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46