The Battle for Wesnoth  1.17.12+dev
playmp_controller.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2022
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() && gamestate().first_human_team_ != -1) {
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 {
104  if(is_host()) {
105  end_turn_enable(true);
106  }
107 
108  while(end_turn_ == END_TURN_NONE) {
109  config cfg;
110  if(network_reader_.read(cfg)) {
112  end_turn();
113  }
114  }
115 
116  play_slice();
117  }
118 }
119 
121 {
122  LOG_NG << "playmp::play_human_turn...";
123  assert(!linger_);
124  assert(gamestate_->init_side_done());
125  assert(gamestate().gamedata_.phase() == game_data::PLAY);
126 
127  mp::ui_alerts::turn_changed(current_team().current_player());
128 
129  LOG_NG << "events::commands_disabled=" << events::commands_disabled;
130 
132 
133  const std::unique_ptr<countdown_clock> timer(saved_game_.mp_settings().mp_countdown
135  : nullptr);
136 
138 
139  if(undo_stack().can_undo()) {
140  // If we reload a networked mp game we cannot undo moves made before the save
141  // because other players already received them
142  if(!current_team().auto_shroud_updates()) {
144  }
145  undo_stack().clear();
146  }
147 
149  execute_gotos();
150  }
151 
152  end_turn_enable(true);
153 
154  while(!should_return_to_play_side()) {
155  try {
160  // Clean undo stack if turn has to be restarted (losing control)
161  if(undo_stack().can_undo()) {
162  font::floating_label flabel(_("Undoing moves not yet transmitted to the server."));
163 
164  color_t color{255, 255, 255, SDL_ALPHA_OPAQUE};
165  flabel.set_color(color);
166  SDL_Rect rect = gui_->map_area();
167  flabel.set_position(rect.w / 2, rect.h / 2);
168  flabel.set_lifetime(2500);
169  flabel.set_clip_rect(rect);
170 
171  font::add_floating_label(flabel);
172  }
173 
174  while(undo_stack().can_undo()) {
175  undo_stack().undo();
176  }
177  }
178 
179  if(timer) {
180  bool time_left = timer->update();
181  if(!time_left) {
183  }
184  }
185  } catch(...) {
186  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();
209  throw;
210  }
211 
213  }
214 }
215 
217 {
218  // Modify the end-turn button
219  if(!is_host()) {
220  std::shared_ptr<gui::button> btn_end = gui_->find_action_button("button-endturn");
221  btn_end->enable(false);
222  }
223 
224  gui_->get_theme().refresh_title2("button-endturn", "title2");
225  gui_->queue_rerender();
226 }
227 
229 {
230  // revert the end-turn button text to its normal label
231  gui_->get_theme().refresh_title2("button-endturn", "title");
232  gui_->queue_rerender();
233  gui_->set_game_mode(game_display::RUNNING);
234 }
235 
237 {
238  LOG_NG << "beginning end-of-scenario linger";
239  linger_ = true;
240 
241  // If we need to set the status depending on the completion state
242  // we're needed here.
243  gui_->set_game_mode(game_display::LINGER);
244 
245  // End all unit moves
247 
249  assert(is_regular_game_end());
250 
251  if(get_end_level_data().transient.reveal_map) {
252  // Change the view of all players and observers
253  // to see the whole map regardless of shroud and fog.
254  update_gui_to_player(gui_->viewing_team(), true);
255  }
256 
257  bool quit;
258  do {
259  quit = true;
260  try {
261  // reimplement parts of play_side()
266  LOG_NG << "finished human turn";
267  } catch(const savegame::load_game_exception&) {
268  LOG_NG << "caught load-game-exception";
269  // this should not happen, the option to load a game is disabled
270  throw;
271  } catch(const leavegame_wesnothd_error& e) {
272  scoped_savegame_snapshot snapshot(*this);
274 
275  if(e.message == "") {
276  save.save_game_interactive(_("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"),
278  } else {
280  _("This game has been ended.\nReason: ") + e.message + _("\nDo you want to save the game?"),
282  }
283  throw;
284  } catch(const ingame_wesnothd_error&) {
285  LOG_NG << "caught network-error-exception";
286  quit = false;
287  }
288  } while(!quit);
289 
291 
292  LOG_NG << "ending end-of-scenario linger";
293 }
294 
296 {
297  // If the host is here we'll never leave since we wait for the host to
298  // upload the next scenario.
299  assert(!is_host());
300 
301  config cfg;
303 
304  while(true) {
305  try {
306  bool res = false;
309 
311  });
312 
314  break;
315  } else {
317  }
318  } catch(const quit_game_exception&) {
319  network_reader_.set_source([this](config& cfg) { return receive_from_wesnothd(cfg); });
321  throw;
322  }
323  }
324 
325  network_reader_.set_source([this](config& cfg) { return receive_from_wesnothd(cfg); });
326 }
327 
329 {
331  // time_left + turn_bonus + (action_bonus * number of actions done)
332  const int new_time_in_secs = (current_team().countdown_time() / 1000)
335 
336  const int new_time
337  = 1000 * std::min<int>(new_time_in_secs, saved_game_.mp_settings().mp_countdown_reservoir_time);
338 
340  current_team().set_countdown_time(new_time);
341 
343  }
344 
345  LOG_NG << "playmp::after_human_turn...";
346 
347  // Normal post-processing for human turns (clear undos, end the turn, etc.)
349 
350  // send one more time to make sure network is up-to-date.
352 }
353 
355 {
356  LOG_NG << "is networked...";
357 
358  end_turn_enable(false);
360 
364  if(!mp_info_ || mp_info_->current_turn == turn()) {
365  skip_replay_ = false;
366  }
367  }
368 
372  }
373  }
374 
375  LOG_NG << "finished networked...";
376 }
377 
378 void playmp_controller::process_oos(const std::string& err_msg) const
379 {
380  // Notify the server of the oos error.
381  config cfg;
382  config& info = cfg.add_child("info");
383  info["type"] = "termination";
384  info["condition"] = "out of sync";
385  send_to_wesnothd(cfg);
386 
387  std::stringstream temp_buf;
388  std::vector<std::string> err_lines = utils::split(err_msg, '\n');
389  temp_buf << _("The game is out of sync, and cannot continue. There are a number of reasons this could happen: this "
390  "can occur if you or another player have modified their game settings. This may mean one of the "
391  "players is attempting to cheat. It could also be due to a bug in the game, but this is less "
392  "likely.\n\nDo you want to save an error log of your game?");
393 
394  if(!err_msg.empty()) {
395  temp_buf << " \n \n"; // and now the "Details:"
396  for(std::vector<std::string>::iterator i = err_lines.begin(); i != err_lines.end(); ++i) {
397  temp_buf << *i << '\n';
398  }
399  temp_buf << " \n";
400  }
401 
402  scoped_savegame_snapshot snapshot(*this);
404 
406 }
407 
408 void playmp_controller::handle_generic_event(const std::string& name)
409 {
411 
412  if(name == "ai_user_interact") {
415  } else if(name == "ai_gamestate_changed") {
417  } else if(name == "host_transfer") {
418  assert(mp_info_);
419  mp_info_->is_host = true;
420  if(linger_) {
421  end_turn_enable(true);
422  }
423  }
424 }
426 {
427  return !mp_info_ || mp_info_->is_host;
428 }
429 
431 {
432  gui_->get_chat_manager().add_chat_message(std::time(nullptr), "", 0,
433  _("This side is in an idle state. To proceed with the game, it must be assigned to another controller. You may "
434  "use :droid, :control or :give_control for example."),
436 }
437 
439 {
440  // mouse_handler expects at least one team for linger mode to work.
441  assert(is_regular_game_end());
442  if(!get_end_level_data().transient.linger_mode || get_teams().empty() || video::headless()) {
443  const bool has_next_scenario
444  = !gamestate().gamedata_.next_scenario().empty() && gamestate().gamedata_.next_scenario() != "null";
445  if(!is_host() && has_next_scenario) {
446  // If we continue without lingering we need to
447  // make sure the host uploads the next scenario
448  // before we attempt to download it.
449  wait_for_upload();
450  }
451  } else {
452  linger();
453  }
454 }
455 
457 {
458  undo_stack().clear();
459  resources::recorder->add_surrender(side_number);
461 }
462 
464 {
466  assert(res != turn_info::PROCESS_END_TURN);
467 
468  if(res == turn_info::PROCESS_END_LINGER) {
469  // Probably some bad OOS, but there is currently no way to recover from this.
470  throw ingame_wesnothd_error("");
471  }
472 
474  player_type_changed_ = true;
475  }
476 }
477 
479 {
481 }
482 
483 void playmp_controller::play_slice(bool is_delay_enabled)
484 {
485  if(!linger_ && !is_replay()) {
486  // receive chat during animations and delay
487  process_network_data(true);
488  // cannot use turn_data_.send_data() here.
490  }
491 
492  playsingle_controller::play_slice(is_delay_enabled);
493 }
494 
496 {
498  return;
499  }
500 
502  config cfg;
503 
504  if(!resources::recorder->at_end()) {
506  } else if(network_reader_.read(cfg)) {
507  res = turn_data_.process_network_data(cfg, chat_only);
508  }
509 
511  network_reader_.push_front(std::move(cfg));
512  } else if(res == turn_info::PROCESS_RESTART_TURN) {
513  player_type_changed_ = true;
514  } else if(res == turn_info::PROCESS_END_TURN) {
516  } else if(res == turn_info::PROCESS_END_LEVEL) {
517  } else if(res == turn_info::PROCESS_END_LINGER) {
518  replay::process_error("Received unexpected next_scenario during the game");
519  }
520 }
521 
523 {
524  return mp_info_ != nullptr;
525 }
526 
527 void playmp_controller::send_to_wesnothd(const config& cfg, const std::string&) const
528 {
529  if(mp_info_ != nullptr) {
531  }
532 }
533 
535 {
536  if(mp_info_ != nullptr) {
537  return mp_info_->connection.receive_data(cfg);
538  } else {
539  return false;
540  }
541 }
bool disable_auto_moves()
Definition: general.cpp:979
void send_data(const configr_of &request)
Queues the given data to be sent to the server.
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:221
void set_all_units_user_end_turn()
Definition: game_board.cpp:87
void process_oos(const std::string &err_msg) const override
Asks the user whether to continue on an OOS error.
void maybe_linger() override
void set_countdown_time(const int amount) const
Definition: team.hpp:199
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)
int countdown_time() const
Definition: team.hpp:198
events::generic_event & host_transfer()
Definition: playturn.hpp:62
void add_countdown_update(int value, int team)
Definition: replay.cpp:240
static lg::log_domain log_engine("engine")
unsigned current_turn
void surrender(int side_number)
std::unique_ptr< game_display > gui_
void process_network_data(bool chat_only=false)
void set_clip_rect(const SDL_Rect &r)
void turn_changed(const std::string &player_name)
logger & info()
Definition: log.cpp:182
bool is_networked_mp() const override
void send_to_wesnothd(const config &cfg, const std::string &packet_type="unknown") const override
std::unique_ptr< hotkey_handler > hotkey_handler_
int first_human_team_
Definition: game_state.hpp:75
virtual void play_linger_turn()
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
void set_lifetime(int lifetime, int fadeout=100)
void sync_non_undoable()
Definition: replay.cpp:915
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:61
void undo()
Undoes the top action on the undo stack.
Definition: undo.cpp:349
And endturn was required eigher by the player, by the ai or by [end_turn].
virtual void play_slice(bool is_delay_enabled=true)
Class for "normal" midgame saves.
Definition: savegame.hpp:252
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
virtual void handle_generic_event(const std::string &name) override
bool receive_data(config &result)
Receives the next pending data pack from the server, if available.
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
bool save_game_interactive(const std::string &message, DIALOG_TYPE dialog_type)
Save a game interactively through the savegame dialog.
Definition: savegame.cpp:384
static std::string _(const char *str)
Definition: gettext.hpp:93
void set_source(source_type source)
void send_data()
Definition: playturn.cpp:80
static config get_update_shroud()
Records that the player has manually updated fog/shroud.
PROCESS_DATA_RESULT
Definition: playturn.hpp:37
void pull_remote_choice() override
bool can_undo() const
std::unique_ptr< game_state > gamestate_
no linger overlay, show fog and shroud.
int action_bonus_count() const
Definition: team.hpp:201
virtual void on_not_observer() override
#define DBG_NG
saved_game & saved_game_
void end_turn_enable(bool enable)
void send_user_choice() override
virtual void check_objectives() override
virtual bool should_return_to_play_side() const override
void throw_quit_game_exception()
void wait_for_upload()
Wait for the host to upload the next scenario.
We received invalid data from wesnothd during a game This means we cannot continue with the game but ...
void add_surrender(int side_number)
Definition: replay.cpp:234
bool is_regular_game_end() const
std::vector< team > & get_teams()
void unblind()
Definition: display.hpp:1024
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
bool headless()
The game is running headless.
Definition: video.cpp:143
void set_position(double xpos, double ypos)
replay * recorder
Definition: resources.cpp:29
virtual void play_human_turn() override
virtual void after_human_turn() override
static bool quit()
Shows the quit confirmation if needed.
void update_gui_to_player(const int team_index, const bool observe=false)
Changes the UI for this client to the passed side index.
playmp_controller(const config &level, saved_game &state_of_game, mp_game_metadata *mp_info)
void set_color(const color_t &color)
bool receive_from_wesnothd(config &cfg) const override
static void process_error(const std::string &msg)
Definition: replay.cpp:199
Exception used to signal that the user has decided to abortt a game, and to load another game instead...
Definition: savegame.hpp:83
An extension of playsingle_controller::hotkey_handler, which has support for MP wesnoth features like...
virtual bool attach_handler(observer *obs)
std::size_t i
Definition: function.cpp:968
When the host uploaded the next scenario this is returned.
Definition: playturn.hpp:43
#define LOG_NG
virtual void play_network_turn() override
Will handle networked turns in descendent classes.
We found a player action in the replay that caused the game to end.
Definition: playturn.hpp:49
void play_slice(bool is_delay_enabled=true) override
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:46
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
static source_type get_source_from_config(config &src)
game_data gamedata_
Definition: game_state.hpp:46
actions::undo_list & undo_stack()
const end_level_data & get_end_level_data() const
const std::string & next_scenario() const
Definition: game_data.hpp:96
config & add_child(config_key_type key)
Definition: config.cpp:514
compression::format save_compression_format()
Definition: game.cpp:844
bool is_replay() const
virtual void play_idle_loop() override
static void display(std::function< void()> f)
game_state & gamestate()
playturn_network_adapter network_reader_
std::vector< std::string > split(const config_attribute_value &val)
game_board board_
Definition: game_state.hpp:47
Various functions that implement the undoing (and redoing) of in-game commands.
void set_action_bonus_count(const int count)
Definition: team.hpp:202
Standard logging facilities (interface).
int current_side() const
Returns the number of the side whose turn it is.
replay_network_sender replay_sender_
virtual bool detach_handler(observer *obs)
std::string message
Definition: exceptions.hpp:30
virtual void do_idle_notification() override
Will handle sending a networked notification in descendent classes.
static PROCESS_DATA_RESULT replay_to_process_data_result(REPLAY_RETURN replayreturn)
Definition: playturn.cpp:396
bool wait_and_receive_data(config &data)
Unlike receive_data, waits until data is available instead of returning immediately.
#define e
int side_number
Definition: game_info.hpp:40
std::size_t turn() const
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
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:59
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
when we couldn&#39;t handle the given action currently.
Definition: playturn.hpp:47
virtual void handle_generic_event(const std::string &name) override
wesnothd_connection & connection
An [end_turn] was added to the replay.
mp_game_metadata * mp_info_