The Battle for Wesnoth  1.15.12+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 "countdown_clock.hpp"
20 #include "display_chat_manager.hpp"
21 #include "floating_label.hpp"
22 #include "game_end_exceptions.hpp"
24 #include "gettext.hpp"
27 #include "log.hpp"
28 #include "mp_ui_alerts.hpp"
29 #include "playturn.hpp"
30 #include "preferences/game.hpp"
31 #include "preferences/general.hpp"
32 #include "replay_helper.hpp"
33 #include "resources.hpp"
34 #include "savegame.hpp"
36 #include "synced_context.hpp"
37 #include "wesnothd_connection.hpp"
38 #include "whiteboard/manager.hpp"
39 
40 static lg::log_domain log_engine("engine");
41 #define LOG_NG LOG_STREAM(info, log_engine)
42 
44  : playsingle_controller(level, state_of_game, mp_info && mp_info->skip_replay)
45  , network_processing_stopped_(false)
46  , blindfold_(*gui_, mp_info && mp_info->skip_replay_blindfolded)
47  , mp_info_(mp_info)
48 {
49  // upgrade hotkey handler to the mp (network enabled) version
50  hotkey_handler_.reset(new hotkey_handler(*this, saved_game_));
51 
52  // turn_data_.set_host(is_host);
54  if(!mp_info || mp_info->current_turn <= turn()) {
55  skip_replay_ = false;
56  }
57 
58  if(gui_->is_blindfolded() && gamestate().first_human_team_ != -1) {
60  }
61 }
62 
64 {
65  // halt and cancel the countdown timer
66  try {
68  } catch(...) {
69  }
70 }
71 
73 {
75  LOG_NG << "network processing activated again";
76 }
77 
79 {
81  LOG_NG << "network processing stopped";
82 }
83 
85 {
87 }
88 
90 {
91  if(gui_->is_blindfolded()) {
93  LOG_NG << "Taking off the blindfold now " << std::endl;
94  gui_->redraw_everything();
95  }
96 }
97 
99 {
100  if(is_host()) {
101  end_turn_enable(true);
102  }
103 
104  while(end_turn_ == END_TURN_NONE) {
105  config cfg;
106  if(network_reader_.read(cfg)) {
108  end_turn();
109  }
110  }
111 
112  play_slice();
113  }
114 }
115 
117 {
118  LOG_NG << "playmp::play_human_turn...\n";
119  assert(!linger_);
120  assert(gamestate_->init_side_done());
121  assert(gamestate().gamedata_.phase() == game_data::PLAY);
122 
123  mp_ui_alerts::turn_changed(current_team().current_player());
124 
125  LOG_NG << "events::commands_disabled=" << events::commands_disabled << "\n";
126 
128 
129  const std::unique_ptr<countdown_clock> timer(saved_game_.mp_settings().mp_countdown
131  : nullptr);
132 
134 
135  if(undo_stack().can_undo()) {
136  // If we reload a networked mp game we cannot undo moves made before the save
137  // because other players already received them
138  if(!current_team().auto_shroud_updates()) {
140  }
141  undo_stack().clear();
142  }
143 
145  execute_gotos();
146  }
147 
148  end_turn_enable(true);
149 
150  while(!should_return_to_play_side()) {
151  try {
156  // Clean undo stack if turn has to be restarted (losing control)
157  if(undo_stack().can_undo()) {
158  font::floating_label flabel(_("Undoing moves not yet transmitted to the server."));
159 
160  color_t color{255, 255, 255, SDL_ALPHA_OPAQUE};
161  flabel.set_color(color);
162  SDL_Rect rect = gui_->map_area();
163  flabel.set_position(rect.w / 2, rect.h / 2);
164  flabel.set_lifetime(2500);
165  flabel.set_clip_rect(rect);
166 
167  font::add_floating_label(flabel);
168  }
169 
170  while(undo_stack().can_undo()) {
171  undo_stack().undo();
172  }
173  }
174 
175  if(timer) {
176  bool time_left = timer->update();
177  if(!time_left) {
179  }
180  }
181  } catch(...) {
183  throw;
184  }
185 
187  }
188 }
189 
191 {
192  LOG_NG << "playmp::play_human_turn...\n";
193 
195 
196  while(!should_return_to_play_side()) {
197  try {
200  SDL_Delay(1);
201  } catch(...) {
203  throw;
204  }
205 
207  }
208 }
209 
211 {
212  // Modify the end-turn button
213  if(!is_host()) {
214  std::shared_ptr<gui::button> btn_end = gui_->find_action_button("button-endturn");
215  btn_end->enable(false);
216  }
217 
218  gui_->get_theme().refresh_title2("button-endturn", "title2");
219  gui_->invalidate_theme();
220  gui_->redraw_everything();
221 }
222 
224 {
225  // revert the end-turn button text to its normal label
226  gui_->get_theme().refresh_title2("button-endturn", "title");
227  gui_->invalidate_theme();
228  gui_->redraw_everything();
229  gui_->set_game_mode(game_display::RUNNING);
230 }
231 
233 {
234  LOG_NG << "beginning end-of-scenario linger\n";
235  linger_ = true;
236 
237  // If we need to set the status depending on the completion state
238  // we're needed here.
239  gui_->set_game_mode(game_display::LINGER);
240 
241  // End all unit moves
243 
245  assert(is_regular_game_end());
246 
247  if(get_end_level_data().transient.reveal_map) {
248  // Change the view of all players and observers
249  // to see the whole map regardless of shroud and fog.
250  update_gui_to_player(gui_->viewing_team(), true);
251  }
252 
253  bool quit;
254  do {
255  quit = true;
256  try {
257  // reimplement parts of play_side()
262  LOG_NG << "finished human turn" << std::endl;
263  } catch(const savegame::load_game_exception&) {
264  LOG_NG << "caught load-game-exception" << std::endl;
265  // this should not happen, the option to load a game is disabled
266  throw;
267  } catch(const leavegame_wesnothd_error& e) {
268  scoped_savegame_snapshot snapshot(*this);
270 
271  if(e.message == "") {
272  save.save_game_interactive(_("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"),
274  } else {
276  _("This game has been ended.\nReason: ") + e.message + _("\nDo you want to save the game?"),
278  }
279  throw;
280  } catch(const ingame_wesnothd_error&) {
281  LOG_NG << "caught network-error-exception" << std::endl;
282  quit = false;
283  }
284  } while(!quit);
285 
287 
288  LOG_NG << "ending end-of-scenario linger\n";
289 }
290 
292 {
293  // If the host is here we'll never leave since we wait for the host to
294  // upload the next scenario.
295  assert(!is_host());
296 
297  config cfg;
299 
300  while(true) {
301  try {
302  bool res = false;
305 
307  });
308 
310  break;
311  } else {
313  }
314  } catch(const quit_game_exception&) {
315  network_reader_.set_source([this](config& cfg) { return receive_from_wesnothd(cfg); });
317  throw;
318  }
319  }
320 
321  network_reader_.set_source([this](config& cfg) { return receive_from_wesnothd(cfg); });
322 }
323 
325 {
327  // time_left + turn_bonus + (action_bonus * number of actions done)
328  const int new_time_in_secs = (current_team().countdown_time() / 1000)
331 
332  const int new_time
333  = 1000 * std::min<int>(new_time_in_secs, saved_game_.mp_settings().mp_countdown_reservoir_time);
334 
336  current_team().set_countdown_time(new_time);
337 
339  }
340 
341  LOG_NG << "playmp::after_human_turn...\n";
342 
343  // Normal post-processing for human turns (clear undos, end the turn, etc.)
345 
346  // send one more time to make sure network is up-to-date.
348 }
349 
351 {
352  LOG_NG << "is networked...\n";
353 
354  end_turn_enable(false);
356 
360  if(!mp_info_ || mp_info_->current_turn == turn()) {
361  skip_replay_ = false;
362  }
363  }
364 
368  }
369  }
370 
371  LOG_NG << "finished networked...\n";
372 }
373 
374 void playmp_controller::process_oos(const std::string& err_msg) const
375 {
376  // Notify the server of the oos error.
377  config cfg;
378  config& info = cfg.add_child("info");
379  info["type"] = "termination";
380  info["condition"] = "out of sync";
381  send_to_wesnothd(cfg);
382 
383  std::stringstream temp_buf;
384  std::vector<std::string> err_lines = utils::split(err_msg, '\n');
385  temp_buf << _("The game is out of sync, and cannot continue. There are a number of reasons this could happen: this "
386  "can occur if you or another player have modified their game settings. This may mean one of the "
387  "players is attempting to cheat. It could also be due to a bug in the game, but this is less "
388  "likely.\n\nDo you want to save an error log of your game?");
389 
390  if(!err_msg.empty()) {
391  temp_buf << " \n \n"; // and now the "Details:"
392  for(std::vector<std::string>::iterator i = err_lines.begin(); i != err_lines.end(); ++i) {
393  temp_buf << *i << '\n';
394  }
395  temp_buf << " \n";
396  }
397 
398  scoped_savegame_snapshot snapshot(*this);
400 
402 }
403 
404 void playmp_controller::handle_generic_event(const std::string& name)
405 {
407 
408  if(name == "ai_user_interact") {
411  } else if(name == "ai_gamestate_changed") {
413  } else if(name == "host_transfer") {
414  assert(mp_info_);
415  mp_info_->is_host = true;
416  if(linger_) {
417  end_turn_enable(true);
418  gui_->invalidate_theme();
419  }
420  }
421 }
423 {
424  return !mp_info_ || mp_info_->is_host;
425 }
426 
428 {
429  gui_->get_chat_manager().add_chat_message(std::time(nullptr), "", 0,
430  _("This side is in an idle state. To proceed with the game, it must be assigned to another controller. You may "
431  "use :droid, :control or :give_control for example."),
433 }
434 
436 {
437  // mouse_handler expects at least one team for linger mode to work.
438  assert(is_regular_game_end());
439  if(!get_end_level_data().transient.linger_mode || get_teams().empty() || gui_->video().faked()) {
440  const bool has_next_scenario
441  = !gamestate().gamedata_.next_scenario().empty() && gamestate().gamedata_.next_scenario() != "null";
442  if(!is_host() && has_next_scenario) {
443  // If we continue without lingering we need to
444  // make sure the host uploads the next scenario
445  // before we attempt to download it.
446  wait_for_upload();
447  }
448  } else {
449  linger();
450  }
451 }
452 
454 {
455  undo_stack().clear();
456  resources::recorder->add_surrender(side_number);
458 }
459 
461 {
463  assert(res != turn_info::PROCESS_END_TURN);
464 
465  if(res == turn_info::PROCESS_END_LINGER) {
466  // Probably some bad OOS, but there is currently no way to recover from this.
467  throw ingame_wesnothd_error("");
468  }
469 
471  player_type_changed_ = true;
472  }
473 }
474 
476 {
478 }
479 
480 void playmp_controller::play_slice(bool is_delay_enabled)
481 {
482  if(!linger_ && !is_replay()) {
483  // receive chat during animations and delay
484  process_network_data(true);
485  // cannot use turn_data_.send_data() here.
487  }
488 
489  playsingle_controller::play_slice(is_delay_enabled);
490 }
491 
493 {
495  return;
496  }
497 
499  config cfg;
500 
501  if(!resources::recorder->at_end()) {
503  } else if(network_reader_.read(cfg)) {
504  res = turn_data_.process_network_data(cfg, chat_only);
505  }
506 
508  network_reader_.push_front(std::move(cfg));
509  } else if(res == turn_info::PROCESS_RESTART_TURN) {
510  player_type_changed_ = true;
511  } else if(res == turn_info::PROCESS_END_TURN) {
513  } else if(res == turn_info::PROCESS_END_LEVEL) {
514  } else if(res == turn_info::PROCESS_END_LINGER) {
515  replay::process_error("Received unexpected next_scenario during the game");
516  }
517 }
518 
520 {
521  return mp_info_ != nullptr;
522 }
523 
524 void playmp_controller::send_to_wesnothd(const config& cfg, const std::string&) const
525 {
526  if(mp_info_ != nullptr) {
528  }
529 }
530 
532 {
533  if(mp_info_ != nullptr) {
534  return mp_info_->connection.receive_data(cfg);
535  } else {
536  return false;
537  }
538 }
bool disable_auto_moves()
Definition: general.cpp:933
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:220
void set_all_units_user_end_turn()
Definition: game_board.cpp:85
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:219
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:218
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")
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)
logger & info()
Definition: log.cpp:88
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:74
virtual void play_linger_turn()
static void progress(loading_stage stage=loading_stage::none)
void set_lifetime(int lifetime, int fadeout=100)
void sync_non_undoable()
Definition: replay.cpp:909
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:252
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:680
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:92
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:36
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:221
virtual void on_not_observer() override
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:230
bool is_regular_game_end() const
std::vector< team > & get_teams()
void unblind()
Definition: display.hpp:1093
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.
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:195
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:940
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()
const end_level_data & get_end_level_data() const
static void save(LexState *ls, int c)
Definition: llex.cpp:57
const std::string & next_scenario() const
Definition: game_data.hpp:95
config & add_child(config_key_type key)
Definition: config.cpp:500
compression::format save_compression_format()
Definition: game.cpp:851
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:46
Various functions that implement the undoing (and redoing) of in-game commands.
void set_action_bonus_count(const int count)
Definition: team.hpp:222
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:29
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:398
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: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:59
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:58
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
wesnothd_connection & connection
An [end_turn] was added to the replay.
mp_game_metadata * mp_info_