The Battle for Wesnoth  1.17.23+dev
playturn.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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 (auto whisper = cfg.optional_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 (auto observer = cfg.optional_child("observer") )
151  {
153  }
154  else if (auto observer_quit = cfg.optional_child("observer_quit"))
155  {
157  }
158  else if (cfg.has_child("leave_game")) {
159  const bool has_reason = cfg.mandatory_child("leave_game").has_attribute("reason");
160  throw leavegame_wesnothd_error(has_reason ? cfg.mandatory_child("leave_game")["reason"].str() : "");
161  }
162  else if (auto turn = cfg.optional_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 (auto change = cfg.optional_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  // TODO: can we replace this with just a call to play_controller::update_viewing_player() ?
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) {
204  }
205  };
206 
207  if (resources::gameboard->is_observer() || (resources::gameboard->teams())[display::get_singleton()->playing_team()].is_local_human()) {
208  disp_set_team(display::get_singleton()->playing_team());
209  } else if (tm.is_local_human()) {
210  disp_set_team(side - 1);
211  }
212 
213  resources::whiteboard->on_change_controller(side,tm);
214 
216 
217  const bool restart = game_display::get_singleton()->playing_side() == side && (was_local || tm.is_local());
218  return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
219  }
220 
221  else if (auto side_drop_c = cfg.optional_child("side_drop"))
222  {
223  // Only the host receives this message when a player leaves/disconnects.
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";
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"];
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);
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.has_child("notify_next_scenario")) {
366  if(chat_only) {
367  return PROCESS_CANNOT_HANDLE;
368  }
369  return PROCESS_END_LINGER;
370  }
371 
372  //If this client becomes the new host, notify the play_controller object about it
373  else if (cfg.has_child("host_transfer")){
375  }
376  else
377  {
378  ERR_NW << "found unknown command:\n" << cfg.debug();
379  }
380 
381  return PROCESS_CONTINUE;
382 }
383 
384 
385 void turn_info::change_side_controller(int side, const std::string& player)
386 {
387  config cfg;
388  config& change = cfg.add_child("change_controller");
389  change["side"] = side;
390  change["player"] = player;
392 }
393 
395 {
396  switch(replayreturn)
397  {
399  return PROCESS_CONTINUE;
403  return PROCESS_END_TURN;
405  return PROCESS_END_LEVEL;
406  default:
407  assert(false);
408  throw "found invalid REPLAY_RETURN";
409  }
410 }
double t
Definition: astarsearch.cpp:65
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:161
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:371
const_attr_itors attribute_range() const
Definition: config.cpp:767
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:321
bool has_attribute(config_key_type key) const
Definition: config.cpp:159
std::size_t all_children_count() const
Definition: config.cpp:311
std::string debug() const
Definition: config.cpp:1248
void clear()
Definition: config.cpp:835
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:389
config & add_child(config_key_type key)
Definition: config.cpp:445
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:122
map_labels & labels()
Definition: display.cpp:2625
void set_team(std::size_t team, bool observe=false)
Sets the team controlled by the player using the computer.
Definition: display.cpp:358
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2328
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:101
virtual void notify_observers()
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:86
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 side_change_controller(int side_num, bool is_local, const std::string &pname, const std::string &controller_type)
Definition: game_board.cpp:238
const std::string & next_scenario() const
Definition: game_data.hpp:131
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:915
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:649
@ MARK_AS_SENT
Definition: replay.hpp:123
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:76
const std::string & current_player() const
Definition: team.hpp:222
bool is_local() const
Definition: team.hpp:249
bool is_local_human() const
Definition: team.hpp:255
~turn_info()
Definition: playturn.cpp:57
events::generic_event host_transfer_
Definition: playturn.hpp:73
PROCESS_DATA_RESULT handle_turn(const config &t, bool chat_only=false)
Definition: playturn.cpp:90
static void change_side_controller(int side, const std::string &player)
Definition: playturn.cpp:385
playturn_network_adapter & network_reader_
Definition: playturn.hpp:75
void send_data()
Definition: playturn.cpp:80
static PROCESS_DATA_RESULT replay_to_process_data_result(REPLAY_RETURN replayreturn)
Definition: playturn.cpp:394
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:61
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:106
turn_info(replay_network_sender &network_sender, playturn_network_adapter &network_reader)
Definition: playturn.cpp:50
replay_network_sender & replay_sender_
Definition: playturn.hpp:71
PROCESS_DATA_RESULT process_network_data_from_reader()
Definition: playturn.cpp:113
PROCESS_DATA_RESULT process_network_data(const config &cfg, bool chat_only=false)
Definition: playturn.cpp:128
#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:555
game_board * gameboard
Definition: resources.cpp:21
game_data * gamedata
Definition: resources.cpp:23
replay * recorder
Definition: resources.cpp:29
actions::undo_list * undo_stack
Definition: resources.cpp:33
play_controller * controller
Definition: resources.cpp:22
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:34
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
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::map< std::string, t_string > string_map
#define ERR_NW
Definition: playturn.cpp:48
static lg::log_domain log_network("network")
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:684
Replay control code.
REPLAY_RETURN
Definition: replay.hpp:157
@ REPLAY_FOUND_DEPENDENT
Definition: replay.hpp:159
@ REPLAY_FOUND_END_LEVEL
Definition: replay.hpp:163
@ REPLAY_RETURN_AT_END
Definition: replay.hpp:158
@ REPLAY_FOUND_END_TURN
Definition: replay.hpp:160
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.