The Battle for Wesnoth  1.19.0-dev
playmp_controller.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2024
3  by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 #include "playmp_controller.hpp"
18 
19 #include "actions/undo.hpp"
20 #include "countdown_clock.hpp"
21 #include "display_chat_manager.hpp"
22 #include "floating_label.hpp"
23 #include "game_end_exceptions.hpp"
25 #include "gettext.hpp"
28 #include "log.hpp"
29 #include "mp_ui_alerts.hpp"
30 #include "playturn.hpp"
31 #include "preferences/game.hpp"
32 #include "preferences/general.hpp"
33 #include "replay_helper.hpp"
34 #include "resources.hpp"
35 #include "savegame.hpp"
37 #include "synced_context.hpp"
38 #include "video.hpp" // only for faked
39 #include "wesnothd_connection.hpp"
40 #include "whiteboard/manager.hpp"
41 
42 static lg::log_domain log_engine("engine");
43 #define LOG_NG LOG_STREAM(info, log_engine)
44 #define DBG_NG LOG_STREAM(debug, log_engine)
45 
47  : playsingle_controller(level, state_of_game, mp_info && mp_info->skip_replay)
48  , network_processing_stopped_(false)
49  , blindfold_(*gui_, mp_info && mp_info->skip_replay_blindfolded)
50  , mp_info_(mp_info)
51 {
52  // upgrade hotkey handler to the mp (network enabled) version
53  hotkey_handler_.reset(new hotkey_handler(*this, saved_game_));
54 
55  // turn_data_.set_host(is_host);
57  if(!mp_info || mp_info->current_turn <= turn()) {
58  skip_replay_ = false;
59  }
60 
61  if(gui_->is_blindfolded() && !is_observer()) {
63  }
64 }
65 
67 {
68  // halt and cancel the countdown timer
69  try {
71  } catch(...) {
72  DBG_NG << "Caught exception in playmp_controller destructor: " << utils::get_unknown_exception_type();
73  }
74 }
75 
77 {
79  LOG_NG << "network processing activated again";
80 }
81 
83 {
85  LOG_NG << "network processing stopped";
86 }
87 
89 {
91 }
92 
94 {
95  if(gui_->is_blindfolded()) {
97  LOG_NG << "Taking off the blindfold now";
98  gui_->queue_rerender();
99  }
100 }
101 
103 {
105  if(replay_controller_.get() != nullptr) {
106  // We have probably been using the mp "back to turn" feature
107  // We continue play since we have reached the end of the replay.
108  replay_controller_.reset();
109  }
110 
111  while( gamestate().in_phase(game_data::GAME_ENDED) && !end_turn_requested_) {
112  config cfg;
113  if(network_reader_.read(cfg)) {
115  end_turn();
116  }
117  }
118 
119  play_slice();
120  }
121 }
122 
124 {
125  LOG_NG << "playmp::play_human_turn...";
126  assert(gamestate().in_phase(game_data::TURN_PLAYING));
127 
128  mp::ui_alerts::turn_changed(current_team().current_player());
129 
130  LOG_NG << "events::commands_disabled=" << events::commands_disabled;
131 
133 
134  const std::unique_ptr<countdown_clock> timer(saved_game_.mp_settings().mp_countdown
136  : nullptr);
137 
139 
140  if(undo_stack().can_undo()) {
141  // If we reload a networked mp game we cannot undo moves made before the save
142  // because other players already received them
143  if(!current_team().auto_shroud_updates()) {
145  }
146  undo_stack().clear();
147  }
148 
150  execute_gotos();
151  }
152 
153  end_turn_enable(true);
154 
155  while(!should_return_to_play_side()) {
156  try {
161  // Clean undo stack if turn has to be restarted (losing control)
162  if(undo_stack().can_undo()) {
163  font::floating_label flabel(_("Undoing moves not yet transmitted to the server."));
164 
165  color_t color{255, 255, 255, SDL_ALPHA_OPAQUE};
166  flabel.set_color(color);
167  SDL_Rect rect = gui_->map_area();
168  flabel.set_position(rect.w / 2, rect.h / 2);
169  flabel.set_lifetime(2500);
170  flabel.set_clip_rect(rect);
171 
172  font::add_floating_label(flabel);
173  }
174 
175  while(undo_stack().can_undo()) {
176  undo_stack().undo();
177  }
178  }
179 
180  if(timer) {
181  bool time_left = timer->update();
182  if(!time_left) {
183  end_turn_requested_ = true;
184  }
185  }
186  } catch(...) {
187  DBG_NG << "Caught exception while playing a side: " << utils::get_unknown_exception_type();
188  throw;
189  }
190 
192  }
193 }
194 
196 {
197  LOG_NG << "playmp::play_human_turn...";
198 
200 
201  while(!should_return_to_play_side()) {
202  try {
205  SDL_Delay(1);
206  } catch(...) {
207  DBG_NG << "Caught exception while playing idle loop: " << utils::get_unknown_exception_type();
208  throw;
209  }
210 
212  }
213 }
214 
216 {
217  LOG_NG << "beginning end-of-scenario linger";
218 
219  // End all unit moves
221 
223 
224  assert(is_regular_game_end());
225 
226  if(get_end_level_data().transient.reveal_map) {
227  // Change the view of all players and observers
228  // to see the whole map regardless of shroud and fog.
229  update_gui_to_player(gui_->viewing_team(), true);
230  }
231 
232  bool quit;
233  do {
234  quit = true;
235  try {
236  // reimplement parts of play_side()
240  LOG_NG << "finished human turn";
241  } catch(const savegame::load_game_exception&) {
242  LOG_NG << "caught load-game-exception";
243  // this should not happen, the option to load a game is disabled
244  throw;
245  } catch(const leavegame_wesnothd_error& e) {
246  scoped_savegame_snapshot snapshot(*this);
248 
249  if(e.message == "") {
250  save.save_game_interactive(_("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"),
252  } else {
254  _("This game has been ended.\nReason: ") + e.message + _("\nDo you want to save the game?"),
256  }
257  throw;
258  } catch(const ingame_wesnothd_error&) {
259  LOG_NG << "caught network-error-exception";
260  quit = false;
261  }
262  } while(!quit);
263 
264  LOG_NG << "ending end-of-scenario linger";
265 }
266 
268 {
270  // If the host is here we'll never leave since we wait for the host to
271  // upload the next scenario.
272  assert(!is_host());
273  // TODO: should we handle the case that we become the ho0st because the host disconnectes here?a
274 
277  while(true) {
279  if(res == turn_info::PROCESS_END_LINGER) {
280  return;
281  } else if (res != turn_info::PROCESS_CONTINUE) {
282  throw quit_game_exception();
283  }
284  SDL_Delay(10);
286  }
287  });
288 }
289 
291 {
293  // time_left + turn_bonus + (action_bonus * number of actions done)
294  const int new_time_in_secs = (current_team().countdown_time() / 1000)
297 
298  const int new_time
299  = 1000 * std::min<int>(new_time_in_secs, saved_game_.mp_settings().mp_countdown_reservoir_time);
300 
302  current_team().set_countdown_time(new_time);
303 
305  }
306 
307  LOG_NG << "playmp::after_human_turn...";
308 
309  // Normal post-processing for human turns (clear undos, end the turn, etc.)
311 
312  // send one more time to make sure network is up-to-date.
314 }
315 
317 {
318  LOG_NG << "is networked...";
319 
320  end_turn_enable(false);
322 
326  if(!mp_info_ || mp_info_->current_turn == turn()) {
327  skip_replay_ = false;
328  }
329  }
330 
334  }
335  }
336 
337  LOG_NG << "finished networked...";
338 }
339 
340 void playmp_controller::process_oos(const std::string& err_msg) const
341 {
342  // Notify the server of the oos error.
343  config cfg;
344  config& info = cfg.add_child("info");
345  info["type"] = "termination";
346  info["condition"] = "out of sync";
347  send_to_wesnothd(cfg);
348 
349  std::stringstream temp_buf;
350  std::vector<std::string> err_lines = utils::split(err_msg, '\n');
351  temp_buf << _("The game is out of sync, and cannot continue. There are a number of reasons this could happen: this "
352  "can occur if you or another player have modified their game settings. This may mean one of the "
353  "players is attempting to cheat. It could also be due to a bug in the game, but this is less "
354  "likely.\n\nDo you want to save an error log of your game?");
355 
356  if(!err_msg.empty()) {
357  temp_buf << " \n \n"; // and now the "Details:"
358  for(std::vector<std::string>::iterator i = err_lines.begin(); i != err_lines.end(); ++i) {
359  temp_buf << *i << '\n';
360  }
361  temp_buf << " \n";
362  }
363 
364  scoped_savegame_snapshot snapshot(*this);
366 
368 }
369 
370 void playmp_controller::handle_generic_event(const std::string& name)
371 {
372  if(name == "ai_user_interact") {
375  } else if(name == "ai_gamestate_changed") {
377  } else if(name == "host_transfer") {
378  assert(mp_info_);
379  mp_info_->is_host = true;
380  if(is_linger_mode()) {
381  end_turn_enable(true);
382  }
383  }
384 }
386 {
387  return !mp_info_ || mp_info_->is_host;
388 }
389 
391 {
392  gui_->get_chat_manager().add_chat_message(std::time(nullptr), "", 0,
393  _("This side is in an idle state. To proceed with the game, it must be assigned to another controller. You may "
394  "use :droid, :control or :give_control for example."),
396 }
397 
399 {
400  // mouse_handler expects at least one team for linger mode to work.
401  assert(is_regular_game_end());
402  if(!get_end_level_data().transient.linger_mode || get_teams().empty() || video::headless()) {
403  const bool has_next_scenario
404  = !gamestate().gamedata_.next_scenario().empty() && gamestate().gamedata_.next_scenario() != "null";
405  if(!is_host() && has_next_scenario) {
406  // If we continue without lingering we need to
407  // make sure the host uploads the next scenario
408  // before we attempt to download it.
409  wait_for_upload();
410  }
411  } else {
412  linger();
413  }
414  end_turn_requested_ = true;
415 }
416 
418 {
419  undo_stack().clear();
422 }
423 
425 {
427  assert(res != turn_info::PROCESS_END_TURN);
428 
429  if(res == turn_info::PROCESS_END_LINGER) {
430  // Probably some bad OOS, but there is currently no way to recover from this.
431  throw ingame_wesnothd_error("");
432  }
433 
435  player_type_changed_ = true;
436  }
437 }
438 
440 {
442 }
443 
444 void playmp_controller::play_slice(bool is_delay_enabled)
445 {
447  // receive chat during animations and delay
448  process_network_data(true);
449  // cannot use turn_data_.send_data() here.
450  // todo: why? The checks in turn_data_.send_data() should be safe enouth.
452  }
453 
454  playsingle_controller::play_slice(is_delay_enabled);
455 }
456 
458 {
460  return;
461  }
462 
464  config cfg;
465 
466  if(!resources::recorder->at_end()) {
468  } else if(network_reader_.read(cfg)) {
469  res = turn_data_.process_network_data(cfg, chat_only);
470  }
471 
473  network_reader_.push_front(std::move(cfg));
474  } else if(res == turn_info::PROCESS_RESTART_TURN) {
475  player_type_changed_ = true;
476  } else if(res == turn_info::PROCESS_END_TURN) {
477  } else if(res == turn_info::PROCESS_END_LEVEL) {
478  } else if(res == turn_info::PROCESS_END_LINGER) {
479  replay::process_error("Received unexpected next_scenario during the game");
480  }
481 }
482 
484 {
485  return mp_info_ != nullptr;
486 }
487 
488 void playmp_controller::send_to_wesnothd(const config& cfg, const std::string&) const
489 {
490  if(mp_info_ != nullptr) {
492  }
493 }
494 
496 {
497  if(mp_info_ != nullptr) {
498  return mp_info_->connection.receive_data(cfg);
499  } else {
500  return false;
501  }
502 }
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:201
void undo()
Undoes the top action on the undo stack.
Definition: undo.cpp:330
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
config & add_child(config_key_type key)
Definition: config.cpp:442
virtual void play_slice(bool is_delay_enabled=true)
virtual bool attach_handler(observer *obs)
virtual bool detach_handler(observer *obs)
void set_lifetime(int lifetime, int fadeout=100)
void set_position(double xpos, double ypos)
void set_color(const color_t &color)
void set_clip_rect(const SDL_Rect &r)
void set_all_units_user_end_turn()
Definition: game_board.cpp:88
@ GAME_ENDED
The game has ended and the user is observing the final state "lingering" The game can be saved here.
Definition: game_data.hpp:103
@ TURN_PLAYING
The User is controlling the game and invoking actions The game can be saved here.
Definition: game_data.hpp:94
@ TURN_ENDED
The turn_end, side_turn_end etc [events] are fired next phase: TURN_STARTING_WAITING (default),...
Definition: game_data.hpp:97
const std::string & next_scenario() const
Definition: game_data.hpp:131
game_board board_
Definition: game_state.hpp:47
game_data gamedata_
Definition: game_state.hpp:46
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
static void display(std::function< void()> f)
static void spin()
Indicate to the player that loading is progressing.
std::vector< team > & get_teams()
std::unique_ptr< hotkey_handler > hotkey_handler_
bool is_linger_mode() const
actions::undo_list & undo_stack()
bool is_observer() const
bool is_regular_game_end() const
saved_game & saved_game_
game_state & gamestate()
std::unique_ptr< game_display > gui_
const end_level_data & get_end_level_data() const
int current_side() const
Returns the number of the side whose turn it is.
bool is_replay() const
bool player_type_changed_
true when the controller of the currently playing side has changed.
std::size_t turn() const
void update_gui_to_player(const int team_index, const bool observe=false)
Changes the UI for this client to the passed side index.
bool can_undo() const
void process_network_data(bool chat_only=false)
void process_oos(const std::string &err_msg) const override
Asks the user whether to continue on an OOS error.
mp_game_metadata * mp_info_
virtual bool is_host() const override
virtual void do_idle_notification() override
Will handle sending a networked notification in descendent classes.
void maybe_linger() override
bool receive_from_wesnothd(config &cfg) const override
virtual void play_idle_loop() override
virtual void handle_generic_event(const std::string &name) override
virtual void play_human_turn() override
void send_to_wesnothd(const config &cfg, const std::string &packet_type="unknown") const override
virtual void play_linger_turn()
void send_user_choice() override
void pull_remote_choice() override
virtual void on_not_observer() override
virtual void after_human_turn() override
bool is_networked_mp() const override
virtual void play_network_turn() override
Will handle networked turns in descendent classes.
void surrender(int side_number)
void wait_for_upload()
Wait for the host to upload the next scenario.
playmp_controller(const config &level, saved_game &state_of_game, mp_game_metadata *mp_info)
void play_slice(bool is_delay_enabled=true) override
void end_turn_enable(bool enable)
std::unique_ptr< replay_controller > replay_controller_
non-null when replay mode in active, is used in singleplayer and for the "back to turn" feature in mu...
virtual void check_objectives() override
playturn_network_adapter network_reader_
Used by turn_data_.
virtual bool should_return_to_play_side() const override
replay_network_sender replay_sender_
Helper to send our actions to the server Used by turn_data_.
turn_info turn_data_
Helper to read and execute (in particular replay data/ user actions ) messsages from the server.
virtual void handle_generic_event(const std::string &name) override
bool end_turn_requested_
true iff the user has pressed the end turn button this turn.
static bool quit()
Shows the quit confirmation if needed.
static config get_update_shroud()
Records that the player has manually updated fog/shroud.
void sync_non_undoable()
Definition: replay.cpp:915
void add_surrender(int side_number)
Definition: replay.cpp:234
static void process_error(const std::string &msg)
Definition: replay.cpp:199
void add_countdown_update(int value, int team)
Definition: replay.cpp:240
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
Class for "normal" midgame saves.
Definition: savegame.hpp:254
Exception used to signal that the user has decided to abortt a game, and to load another game instead...
Definition: savegame.hpp:85
bool save_game_interactive(const std::string &message, DIALOG_TYPE dialog_type)
Save a game interactively through the savegame dialog.
Definition: savegame.cpp:383
static bool run_and_store(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
void set_action_bonus_count(const int count)
Definition: team.hpp:202
int action_bonus_count() const
Definition: team.hpp:201
void set_countdown_time(const int amount) const
Definition: team.hpp:199
int countdown_time() const
Definition: team.hpp:198
events::generic_event & host_transfer()
Definition: playturn.hpp:62
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_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
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
bool receive_data(config &result)
Receives the next pending data pack from the server, if available.
void send_data(const configr_of &request)
Queues the given data to be sent to the server.
std::size_t i
Definition: function.cpp:968
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
static std::string _(const char *str)
Definition: gettext.hpp:93
An extension of playsingle_controller::hotkey_handler, which has support for MP wesnoth features like...
Standard logging facilities (interface).
int side_number
Definition: game_info.hpp:40
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
logger & info()
Definition: log.cpp:317
void turn_changed(const std::string &player_name)
compression::format save_compression_format()
Definition: game.cpp:843
bool disable_auto_moves()
Definition: general.cpp:971
replay * recorder
Definition: resources.cpp:29
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::vector< std::string > split(const config_attribute_value &val)
bool headless()
The game is running headless.
Definition: video.cpp:145
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
static lg::log_domain log_engine("engine")
#define DBG_NG
#define LOG_NG
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:684
void unblind()
Definition: display.hpp:1029
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
We received invalid data from wesnothd during a game This means we cannot continue with the game but ...
wesnothd_connection & connection
unsigned current_turn
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
Various functions that implement the undoing (and redoing) of in-game commands.
#define e