The Battle for Wesnoth  1.19.0-dev
playturn.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 
41 #include <cassert> // for assert
42 #include <ctime> // for time
43 #include <vector> // for vector
44 
45 static lg::log_domain log_network("network");
46 #define ERR_NW LOG_STREAM(err, log_network)
47 
49  replay_sender_(replay_sender),
50  host_transfer_("host_transfer"),
51  network_reader_(network_reader)
52 {
53 }
54 
56 {
57 }
58 
60 {
61  //there should be nothing left on the replay and we should get turn_info::PROCESS_CONTINUE back.
63  if(resources::controller->is_networked_mp()) {
64 
65  //receive data first, and then send data. When we sent the end of
66  //the AI's turn, we don't want there to be any chance where we
67  //could get data back pertaining to the next turn.
68  config cfg;
69  while( (retv == turn_info::PROCESS_CONTINUE) && network_reader_.read(cfg)) {
70  retv = process_network_data(cfg);
71  cfg.clear();
72  }
73  send_data();
74  }
75  return retv;
76 }
77 
79 {
81  if ( !send_everything ) {
83  } else {
85  }
86 }
87 
89 {
90  //t can contain a [command] or a [upload_log]
91  assert(t.all_children_count() == 1);
92 
93  if(!t.child_or_empty("command").has_child("speak") && chat_only) {
94  return PROCESS_CANNOT_HANDLE;
95  }
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().empty());
131  if(!resources::recorder->at_end())
132  {
133  ERR_NW << "processing network data while still having data on the replay.";
134  }
135 
136  if (const auto message = cfg.optional_child("message"))
137  {
138  game_display::get_singleton()->get_chat_manager().add_chat_message(std::time(nullptr), message.value()["sender"], message.value()["side"],
139  message.value()["message"], events::chat_handler::MESSAGE_PUBLIC,
141  }
142  else if (auto whisper = cfg.optional_child("whisper") /*&& is_observer()*/)
143  {
144  game_display::get_singleton()->get_chat_manager().add_chat_message(std::time(nullptr), "whisper: " + whisper["sender"].str(), 0,
145  whisper["message"], events::chat_handler::MESSAGE_PRIVATE,
147  }
148  else if (auto observer = cfg.optional_child("observer") )
149  {
151  }
152  else if (auto observer_quit = cfg.optional_child("observer_quit"))
153  {
155  }
156  else if (cfg.has_child("leave_game")) {
157  const bool has_reason = cfg.mandatory_child("leave_game").has_attribute("reason");
158  throw leavegame_wesnothd_error(has_reason ? cfg.mandatory_child("leave_game")["reason"].str() : "");
159  }
160  else if (auto turn = cfg.optional_child("turn"))
161  {
162  return handle_turn(*turn, chat_only);
163  }
164  else if (cfg.has_child("whiteboard"))
165  {
166  set_scontext_unsynced scontext;
167  resources::whiteboard->process_network_data(cfg);
168  }
169  else if (auto change = cfg.optional_child("change_controller"))
170  {
171  if(change->empty()) {
172  ERR_NW << "Bad [change_controller] signal from server, [change_controller] tag was empty.";
173  return PROCESS_CONTINUE;
174  }
175 
176  const int side = change["side"].to_int();
177  const bool is_local = change["is_local"].to_bool();
178  const std::string player = change["player"];
179  const std::string controller_type = change["controller"];
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();
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, controller_type);
190 
191  if (!was_local && tm.is_local()) {
193  }
194 
195  // TODO: can we replace this with just a call to play_controller::update_viewing_player() ?
196  auto disp_set_team = [](int side_index) {
197  const bool side_changed = static_cast<int>(display::get_singleton()->viewing_team()) != side_index;
198  display::get_singleton()->set_team(side_index);
199 
200  if(side_changed) {
202  }
203  };
204 
205  if (resources::gameboard->is_observer() || (resources::gameboard->teams())[display::get_singleton()->playing_team()].is_local_human()) {
206  disp_set_team(display::get_singleton()->playing_team());
207  } else if (tm.is_local_human()) {
208  disp_set_team(side - 1);
209  }
210 
211  resources::whiteboard->on_change_controller(side,tm);
212 
214 
215  const bool restart = game_display::get_singleton()->playing_side() == side && (was_local || tm.is_local());
216  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
217  }
218 
219  else if (auto side_drop_c = cfg.optional_child("side_drop"))
220  {
221  // Only the host receives this message when a player leaves/disconnects.
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.has_child("notify_next_scenario")) {
364  if(chat_only) {
365  return PROCESS_CANNOT_HANDLE;
366  }
367  return PROCESS_END_LINGER;
368  }
369 
370  //If this client becomes the new host, notify the play_controller object about it
371  else if (cfg.has_child("host_transfer")){
373  }
374  else
375  {
376  ERR_NW << "found unknown command:\n" << cfg.debug();
377  }
378 
379  return PROCESS_CONTINUE;
380 }
381 
382 
383 void turn_info::change_side_controller(int side, const std::string& player)
384 {
385  config cfg;
386  config& change = cfg.add_child("change_controller");
387  change["side"] = side;
388  change["player"] = player;
390 }
391 
393 {
394  switch(replayreturn)
395  {
397  return PROCESS_CONTINUE;
401  return PROCESS_END_TURN;
403  return PROCESS_END_LEVEL;
404  default:
405  assert(false);
406  throw "found invalid REPLAY_RETURN";
407  }
408 }
double t
Definition: astarsearch.cpp:63
bool can_undo() const
True if there are actions that can be undone.
Definition: undo.hpp:99
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:367
const_attr_itors attribute_range() const
Definition: config.cpp:763
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:317
bool has_attribute(config_key_type key) const
Definition: config.cpp:155
std::size_t all_children_count() const
Definition: config.cpp:307
std::string debug() const
Definition: config.cpp:1244
void clear()
Definition: config.cpp:831
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:385
config & add_child(config_key_type key)
Definition: config.cpp:441
void remove_observer(const std::string &name)
void add_observer(const std::string &name)
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)
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:116
map_labels & labels()
Definition: display.cpp:2615
void set_team(std::size_t team, bool observe=false)
Sets the team controlled by the player using the computer.
Definition: display.cpp:353
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2320
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:95
virtual void notify_observers()
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:79
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:223
void side_change_controller(int side_num, bool is_local, const std::string &pname, const std::string &controller_type)
Definition: game_board.cpp:239
const std::string & next_scenario() const
Definition: game_data.hpp:130
virtual int playing_side() const override
The playing team is the team whose turn it is.
static game_display * get_singleton()
display_chat_manager & get_chat_manager()
bool show(const unsigned auto_close_time=0)
Shows the window.
A simple one-column listbox with OK and Cancel buttons.
void set_single_button(bool value)
Sets whether the Cancel button should be hidden or not.
int selected_index() const
Returns the selected item index after displaying.
void recalculate_labels()
Definition: label.cpp:245
virtual void on_not_observer()=0
virtual void send_to_wesnothd(const config &, const std::string &="unknown") const
void sync_non_undoable()
Definition: replay.cpp:914
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:648
@ MARK_AS_SENT
Definition: replay.hpp:119
An object to leave the synced context during draw or unsynced wml items when we don’t know whether we...
static bool undo_blocked()
static bool is_unsynced()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
const std::string & current_player() const
Definition: team.hpp:220
bool is_local() const
Definition: team.hpp:247
bool is_local_human() const
Definition: team.hpp:253
~turn_info()
Definition: playturn.cpp:55
events::generic_event host_transfer_
Definition: playturn.hpp:73
PROCESS_DATA_RESULT handle_turn(const config &t, bool chat_only=false)
Definition: playturn.cpp:88
static void change_side_controller(int side, const std::string &player)
Definition: playturn.cpp:383
playturn_network_adapter & network_reader_
Definition: playturn.hpp:75
void send_data()
Definition: playturn.cpp:78
static PROCESS_DATA_RESULT replay_to_process_data_result(REPLAY_RETURN replayreturn)
Definition: playturn.cpp:392
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:59
PROCESS_DATA_RESULT
Definition: playturn.hpp:38
@ PROCESS_END_TURN
Definition: playturn.hpp:41
@ PROCESS_FOUND_DEPENDENT
When we couldn't process the network data because we found a dependent command, this should only happ...
Definition: playturn.hpp:45
@ PROCESS_CANNOT_HANDLE
when we couldn't handle the given action currently.
Definition: playturn.hpp:47
@ PROCESS_RESTART_TURN
Definition: playturn.hpp:40
@ PROCESS_END_LINGER
When the host uploaded the next scenario this is returned.
Definition: playturn.hpp:43
@ PROCESS_END_LEVEL
We found a player action in the replay that caused the game to end.
Definition: playturn.hpp:49
@ PROCESS_CONTINUE
Definition: playturn.hpp:39
void do_save()
Definition: playturn.cpp:104
turn_info(replay_network_sender &network_sender, playturn_network_adapter &network_reader)
Definition: playturn.cpp:48
replay_network_sender & replay_sender_
Definition: playturn.hpp:71
PROCESS_DATA_RESULT process_network_data_from_reader()
Definition: playturn.cpp:111
PROCESS_DATA_RESULT process_network_data(const config &cfg, bool chat_only=false)
Definition: playturn.cpp:126
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
void throw_quit_game_exception()
static std::string _(const char *str)
Definition: gettext.hpp:93
Standard logging facilities (interface).
std::string observer
bool message_bell()
Definition: general.cpp:723
const config & options()
Definition: game.cpp:552
game_board * gameboard
Definition: resources.cpp:20
game_data * gamedata
Definition: resources.cpp:22
replay * recorder
Definition: resources.cpp:28
actions::undo_list * undo_stack
Definition: resources.cpp:32
play_controller * controller
Definition: resources.cpp:21
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
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:70
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::map< std::string, t_string > string_map
#define ERR_NW
Definition: playturn.cpp:46
static lg::log_domain log_network("network")
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:683
Replay control code.
REPLAY_RETURN
Definition: replay.hpp:153
@ REPLAY_FOUND_DEPENDENT
Definition: replay.hpp:155
@ REPLAY_FOUND_END_LEVEL
Definition: replay.hpp:159
@ REPLAY_RETURN_AT_END
Definition: replay.hpp:154
@ REPLAY_FOUND_END_TURN
Definition: replay.hpp:156
We received invalid data from wesnothd during a game This means we cannot continue with the game but ...
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
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
Various functions that implement the undoing (and redoing) of in-game commands.