The Battle for Wesnoth  1.15.3+dev
playmp_controller.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2018 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
3  wesnoth playlevel Copyright (C) 2003 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 "playmp_controller.hpp"
17 
18 #include "actions/undo.hpp"
19 #include "display_chat_manager.hpp"
20 #include "floating_label.hpp"
21 #include "game_end_exceptions.hpp"
23 #include "gettext.hpp"
25 #include "log.hpp"
26 #include "mp_ui_alerts.hpp"
27 #include "playturn.hpp"
28 #include "preferences/general.hpp"
29 #include "preferences/game.hpp"
31 #include "resources.hpp"
32 #include "savegame.hpp"
34 #include "whiteboard/manager.hpp"
35 #include "countdown_clock.hpp"
36 #include "synced_context.hpp"
37 #include "replay_helper.hpp"
38 #include "wesnothd_connection.hpp"
39 
40 static lg::log_domain log_engine("engine");
41 #define LOG_NG LOG_STREAM(info, log_engine)
42 
44  saved_game& state_of_game,
45  const ter_data_cache & tdata,
46  mp_campaign_info* mp_info)
47  : playsingle_controller(level, state_of_game, tdata, 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  hotkey_handler_.reset(new hotkey_handler(*this, saved_game_)); //upgrade hotkey handler to the mp (network enabled) version
53 
54  //turn_data_.set_host(is_host);
56  if (!mp_info || mp_info->current_turn <= turn()) {
57  skip_replay_ = false;
58  }
59 
60  if (gui_->is_blindfolded() && gamestate().first_human_team_ != -1) {
62  }
63 }
64 
66  //halt and cancel the countdown timer
67  try {
69  } catch (...) {}
70 }
71 
74  LOG_NG << "network processing activated again";
75 }
76 
79  LOG_NG << "network processing stopped";
80 }
81 
84 }
85 
87  if (gui_->is_blindfolded()) {
89  LOG_NG << "Taking off the blindfold now " << std::endl;
90  gui_->redraw_everything();
91  }
92 }
93 
95 {
96  if (is_host()) {
97  end_turn_enable(true);
98  }
99 
100  while(end_turn_ == END_TURN_NONE) {
101  config cfg;
102  if(network_reader_.read(cfg)) {
104  {
105  end_turn();
106  }
107  }
108  play_slice();
109  }
110 }
111 
113 {
114  LOG_NG << "playmp::play_human_turn...\n";
115  assert(!linger_);
116  assert(gamestate_->init_side_done());
117  assert(gamestate().gamedata_.phase() == game_data::PLAY);
118 
119  mp_ui_alerts::turn_changed(current_team().current_player());
120 
121  LOG_NG << "events::commands_disabled=" << events::commands_disabled <<"\n";
122 
124  const std::unique_ptr<countdown_clock> timer(saved_game_.mp_settings().mp_countdown
126  : nullptr);
128  if(undo_stack().can_undo()) {
129  // If we reload a networked mp game we cannot undo moves made before the save
130  // because other players already received them
131  if(!current_team().auto_shroud_updates()) {
133  }
134  undo_stack().clear();
135  }
137  execute_gotos();
138  }
139 
140  end_turn_enable(true);
141  while(!should_return_to_play_side()) {
142  try {
147  {
148  // Clean undo stack if turn has to be restarted (losing control)
149  if ( undo_stack().can_undo() )
150  {
151  font::floating_label flabel(_("Undoing moves not yet transmitted to the server."));
152 
153  color_t color {255,255,255,SDL_ALPHA_OPAQUE};
154  flabel.set_color(color);
155  SDL_Rect rect = gui_->map_area();
156  flabel.set_position(rect.w/2, rect.h/2);
157  flabel.set_lifetime(2500);
158  flabel.set_clip_rect(rect);
159 
160  font::add_floating_label(flabel);
161  }
162 
163  while( undo_stack().can_undo() )
164  undo_stack().undo();
165 
166  }
167  if(timer)
168  {
169  bool time_left = timer->update();
170  if(!time_left)
171  {
173  }
174  }
175  }
176  catch(...)
177  {
179  throw;
180  }
182  }
183 }
184 
186 {
187  LOG_NG << "playmp::play_human_turn...\n";
188 
190 
191  while (!should_return_to_play_side())
192  {
193  try
194  {
197  SDL_Delay(1);
198  }
199  catch(...)
200  {
202  throw;
203  }
205  }
206 }
207 
209 {
210  // Modify the end-turn button
211  if (! is_host()) {
212  std::shared_ptr<gui::button> btn_end = gui_->find_action_button("button-endturn");
213  btn_end->enable(false);
214  }
215  gui_->get_theme().refresh_title2("button-endturn", "title2");
216  gui_->invalidate_theme();
217  gui_->redraw_everything();
218 }
219 
221 {
222  // revert the end-turn button text to its normal label
223  gui_->get_theme().refresh_title2("button-endturn", "title");
224  gui_->invalidate_theme();
225  gui_->redraw_everything();
226  gui_->set_game_mode(game_display::RUNNING);
227 }
228 
230 {
231  LOG_NG << "beginning end-of-scenario linger\n";
232  linger_ = true;
233  // If we need to set the status depending on the completion state
234  // we're needed here.
235  gui_->set_game_mode(game_display::LINGER);
236  // End all unit moves
238 
240  assert(is_regular_game_end());
241  if ( get_end_level_data_const().transient.reveal_map ) {
242  // Change the view of all players and observers
243  // to see the whole map regardless of shroud and fog.
244  update_gui_to_player(gui_->viewing_team(), true);
245  }
246  bool quit;
247  do {
248  quit = true;
249  try {
250  // reimplement parts of play_side()
255  LOG_NG << "finished human turn" << std::endl;
256  } catch (const savegame::load_game_exception&) {
257  LOG_NG << "caught load-game-exception" << std::endl;
258  // this should not happen, the option to load a game is disabled
259  throw;
260  } catch (const leavegame_wesnothd_error& e) {
261  scoped_savegame_snapshot snapshot(*this);
263  if(e.message == "") {
264  save.save_game_interactive(_("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"), savegame::savegame::YES_NO);
265  } else {
266  save.save_game_interactive(_("This game has been ended.\nReason: ")+e.message+_("\nDo you want to save the game?"), savegame::savegame::YES_NO);
267  }
268  throw;
269  } catch (const ingame_wesnothd_error&) {
270  LOG_NG << "caught network-error-exception" << std::endl;
271  quit = false;
272  }
273  } while (!quit);
274 
276 
277  LOG_NG << "ending end-of-scenario linger\n";
278 }
279 
281 {
282  // If the host is here we'll never leave since we wait for the host to
283  // upload the next scenario.
284  assert(!is_host());
285 
286  config cfg;
288  while(true) {
289  try {
290  const bool res =
292 
293  if(res) {
295  break;
296  }
297  }
298  else
299  {
301  }
302 
303  } catch(const quit_game_exception&) {
304  network_reader_.set_source([this](config& cfg) { return receive_from_wesnothd(cfg);});
306  throw;
307  }
308  }
309  network_reader_.set_source([this](config& cfg) { return receive_from_wesnothd(cfg);});
310 }
311 
314  {
315  //time_left + turn_bonus + (action_bonus * number of actions done)
316  const int new_time_in_secs = (current_team().countdown_time() / 1000)
319  const int new_time = 1000 * std::min<int>(new_time_in_secs, saved_game_.mp_settings().mp_countdown_reservoir_time);
320 
322  current_team().set_countdown_time(new_time);
324  }
325  LOG_NG << "playmp::after_human_turn...\n";
326 
327  // Normal post-processing for human turns (clear undos, end the turn, etc.)
329  //send one more time to make sure network is up-to-date.
331 
332 }
333 
335  LOG_NG << "is networked...\n";
336 
337  end_turn_enable(false);
339 
341  {
344  if (!mp_info_ || mp_info_->current_turn == turn()) {
345  skip_replay_ = false;
346  }
347  }
348 
352  }
353  }
354 
355  LOG_NG << "finished networked...\n";
356 }
357 
358 
359 void playmp_controller::process_oos(const std::string& err_msg) const {
360  // Notify the server of the oos error.
361  config cfg;
362  config& info = cfg.add_child("info");
363  info["type"] = "termination";
364  info["condition"] = "out of sync";
365  send_to_wesnothd(cfg);
366 
367  std::stringstream temp_buf;
368  std::vector<std::string> err_lines = utils::split(err_msg,'\n');
369  temp_buf << _("The game is out of sync, and cannot continue. There are a number of reasons this could happen: this can occur if you or another player have modified their game settings. This may mean one of the players is attempting to cheat. It could also be due to a bug in the game, but this is less likely.\n\nDo you want to save an error log of your game?");
370  if(!err_msg.empty()) {
371  temp_buf << " \n \n"; //and now the "Details:"
372  for(std::vector<std::string>::iterator i=err_lines.begin(); i!=err_lines.end(); ++i)
373  {
374  temp_buf << *i << '\n';
375  }
376  temp_buf << " \n";
377  }
378  scoped_savegame_snapshot snapshot(*this);
381 }
382 
383 void playmp_controller::handle_generic_event(const std::string& name){
385 
386  if (name == "ai_user_interact")
387  {
390  }
391  else if (name == "ai_gamestate_changed")
392  {
394  }
395  else if (name == "host_transfer"){
396  assert(mp_info_);
397  mp_info_->is_host = true;
398  if (linger_){
399  end_turn_enable(true);
400  gui_->invalidate_theme();
401  }
402  }
403 }
405 {
406  return !mp_info_ || mp_info_->is_host;
407 }
408 
410 {
411  gui_->get_chat_manager().add_chat_message(std::time(nullptr), "", 0,
412  _("This side is in an idle state. To proceed with the game, it must be assigned to another controller. You may use :droid, :control or :give_control for example."),
414 }
415 
417 {
418  // mouse_handler expects at least one team for linger mode to work.
419  assert(is_regular_game_end());
420  if (!get_end_level_data_const().transient.linger_mode || gamestate().board_.teams().empty() || gui_->video().faked()) {
421  const bool has_next_scenario = !gamestate().gamedata_.next_scenario().empty() && gamestate().gamedata_.next_scenario() != "null";
422  if(!is_host() && has_next_scenario) {
423  // If we continue without lingering we need to
424  // make sure the host uploads the next scenario
425  // before we attempt to download it.
426  wait_for_upload();
427  }
428  } else {
429  linger();
430  }
431 }
432 
434  undo_stack().clear();
435  resources::recorder->add_surrender(side_number);
437 }
438 
440 {
442  assert(res != turn_info::PROCESS_END_TURN);
443 
444  if(res == turn_info::PROCESS_END_LINGER) {
445  // Probably some bad OOS, but there is currently no way to recover from this.
446  throw ingame_wesnothd_error("");
447  }
448 
450  {
451  player_type_changed_ = true;
452  }
453 }
454 
456 {
458 }
459 
460 void playmp_controller::play_slice(bool is_delay_enabled)
461 {
462  if(!linger_ && !is_replay()) {
463  //receive chat during animations and delay
464  process_network_data(true);
465  //cannot use turn_data_.send_data() here.
467  }
468  playsingle_controller::play_slice(is_delay_enabled);
469 }
470 
472 {
474  return;
475  }
477  config cfg;
478  if(!resources::recorder->at_end()) {
480  }
481  else if(network_reader_.read(cfg)) {
482  res = turn_data_.process_network_data(cfg, chat_only);
483  }
484 
486  network_reader_.push_front(std::move(cfg));
487  }
488  else if (res == turn_info::PROCESS_RESTART_TURN) {
489  player_type_changed_ = true;
490  }
491  else if (res == turn_info::PROCESS_END_TURN) {
493  }
494  else if (res == turn_info::PROCESS_END_LEVEL) {
495  }
496  else if (res == turn_info::PROCESS_END_LINGER) {
497  replay::process_error("Received unexpected next_scenario during the game");
498  }
499 }
501 {
502  return mp_info_ != nullptr;
503 }
504 
505 void playmp_controller::send_to_wesnothd(const config& cfg, const std::string&) const
506 {
507  if (mp_info_ != nullptr) {
509  }
510 }
512 {
513  if (mp_info_ != nullptr) {
514  return mp_info_->connection.receive_data(cfg);
515  }
516  else {
517  return false;
518  }
519 }
bool disable_auto_moves()
Definition: general.cpp:920
void send_data(const configr_of &request)
Exception used to escape form the ai or ui code to playsingle_controller::play_side.
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:220
void set_all_units_user_end_turn()
Definition: game_board.cpp:80
void process_oos(const std::string &err_msg) const override
Asks the user whether to continue on an OOS error.
void maybe_linger() override
playmp_controller(const config &level, saved_game &state_of_game, const ter_data_cache &tdata, mp_campaign_info *mp_info)
void set_countdown_time(const int amount) const
Definition: team.hpp:211
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:210
events::generic_event & host_transfer()
Definition: playturn.hpp:61
void turn_changed(const std::string &player_name)
void add_countdown_update(int value, int team)
Definition: replay.cpp:236
static lg::log_domain log_engine("engine")
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)
logger & info()
Definition: log.cpp:90
bool is_networked_mp() const override
bool fetch_data_with_loading_screen(config &cfg, loading_stage stage)
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:72
virtual void play_linger_turn()
void sync_non_undoable()
Definition: replay.cpp:907
PROCESS_DATA_RESULT sync_network()
Definition: playturn.cpp:61
void undo()
Undoes the top action on the undo stack.
Definition: undo.cpp:348
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:262
virtual void handle_generic_event(const std::string &name) override
bool receive_data(config &result)
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:678
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:373
const end_level_data & get_end_level_data_const() const
void set_source(source_type source)
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
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:36
void pull_remote_choice() override
bool can_undo() const
std::unique_ptr< game_state > gamestate_
-file pathfind.hpp
no linger overlay, show fog and shroud.
int action_bonus_count() const
Definition: team.hpp:213
virtual void on_not_observer() override
saved_game & saved_game_
void end_turn_enable(bool enable)
void send_user_choice() override
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
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.
void set_lifetime(int lifetime)
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:230
bool is_regular_game_end() const
void unblind()
Definition: display.hpp:1088
mp_campaign_info * mp_info_
unsigned current_turn
void set_position(double xpos, double ypos)
replay * recorder
Definition: resources.cpp:28
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.
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:195
Exception used to signal that the user has decided to abortt a game, and to load another game instead...
Definition: savegame.hpp:93
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:933
When the host uploaded the next scenario this is returned.
Definition: playturn.hpp:42
#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:48
void play_slice(bool is_delay_enabled=true) override
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:45
actions::undo_list & undo_stack()
static void save(LexState *ls, int c)
Definition: llex.cpp:57
const std::string & next_scenario() const
Definition: game_data.hpp:93
config & add_child(config_key_type key)
Definition: config.cpp:476
compression::format save_compression_format()
Definition: game.cpp:885
bool is_replay() const
virtual void play_idle_loop() override
game_state & gamestate()
wesnothd_connection & connection
playturn_network_adapter network_reader_
game_board board_
Definition: game_state.hpp:46
Various functions that implement the undoing (and redoing) of in-game commands.
void set_action_bonus_count(const int count)
Definition: team.hpp:214
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:31
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:397
#define e
int side_number
Definition: game_info.hpp:39
std::size_t turn() const
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
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:24
when we couldn&#39;t handle the given action currently.
Definition: playturn.hpp:46
virtual void handle_generic_event(const std::string &name) override
An [end_turn] was added to the replay.
std::shared_ptr< terrain_type_data > ter_data_cache