The Battle for Wesnoth  1.19.5+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 "formula/string_utils.hpp" // for VGETTEXT
24 #include "game_end_exceptions.hpp"
26 #include "gettext.hpp"
30 #include "log.hpp"
31 #include "map/label.hpp"
32 #include "mp_ui_alerts.hpp"
34 #include "replay_helper.hpp"
35 #include "resources.hpp"
36 #include "savegame.hpp"
38 #include "synced_context.hpp"
39 #include "video.hpp" // only for faked
40 #include "wesnothd_connection.hpp"
41 #include "whiteboard/manager.hpp"
42 
43 static lg::log_domain log_engine("engine");
44 static lg::log_domain log_network("network");
45 
46 #define LOG_NG LOG_STREAM(info, log_engine)
47 #define DBG_NG LOG_STREAM(debug, log_engine)
48 #define ERR_NW LOG_STREAM(err, log_network)
49 
51  : playsingle_controller(level, state_of_game)
52  , network_processing_stopped_(false)
53  , next_scenario_notified_(false)
54  , blindfold_(*gui_, mp_info && mp_info->skip_replay_blindfolded)
55  , network_reader_([this](config& cfg) { return receive_from_wesnothd(cfg); })
56  , mp_info_(mp_info)
57 {
58  // upgrade hotkey handler to the mp (network enabled) version
59  hotkey_handler_.reset(new hotkey_handler(*this, saved_game_));
60 
61  skip_replay_ = mp_info && mp_info->skip_replay && mp_info->current_turn > turn();
62 
63  if(gui_->is_blindfolded() && !is_observer()) {
65  }
66 }
67 
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";
94  gui_->queue_rerender();
95  }
96 }
97 
99 {
100  LOG_NG << "playmp::play_human_turn...";
101  assert(gamestate().in_phase(game_data::TURN_PLAYING));
102 
103  mp::ui_alerts::turn_changed(current_team().current_player());
104 
105  LOG_NG << "events::commands_disabled=" << events::commands_disabled;
106 
108 
109  utils::optional<countdown_clock> timer;
111  timer.emplace(current_team());
112  }
113 
115 
116  if(undo_stack().can_undo()) {
117  // If we reload a networked mp game we cannot undo moves made before the save
118  // because other players already received them
119  if(!current_team().auto_shroud_updates()) {
121  }
122  undo_stack().clear();
123  }
124 
125  if(!prefs::get().disable_auto_moves()) {
126  execute_gotos();
127  }
128 
129  end_turn_enable(true);
130 
131  while(!should_return_to_play_side()) {
132  try {
137  // Clean undo stack if turn has to be restarted (losing control)
138  if(undo_stack().can_undo()) {
139  gui_->announce(_("Undoing moves not yet transmitted to the server."), font::NORMAL_COLOR);
140  }
141 
142  while(undo_stack().can_undo()) {
143  undo_stack().undo();
144  }
145  }
146 
147  if(timer) {
148  bool time_left = timer->update();
149  if(!time_left) {
150  end_turn_requested_ = true;
151  }
152  }
153  } catch(...) {
154  DBG_NG << "Caught exception while playing a side: " << utils::get_unknown_exception_type();
155  throw;
156  }
157  }
158 }
159 
161 {
162  LOG_NG << "playmp::play_human_turn...";
163 
165 
166  while(!should_return_to_play_side()) {
167  try {
170  using namespace std::chrono_literals;
171  std::this_thread::sleep_for(1ms); // TODO: why?
172  } catch(...) {
173  DBG_NG << "Caught exception while playing idle loop: " << utils::get_unknown_exception_type();
174  throw;
175  }
176  }
177 }
178 
180 {
181  send_actions();
182 
185  while(!next_scenario_notified_ && !is_host()) {
187  using namespace std::chrono_literals;
188  std::this_thread::sleep_for(10ms);
190  }
191  });
192 }
193 
195 {
197  // time_left + turn_bonus + (action_bonus * number of actions done)
198  auto new_time = current_team().countdown_time()
201 
202  new_time
203  = std::min<std::chrono::milliseconds>(new_time, saved_game_.mp_settings().mp_countdown_reservoir_time);
204 
206  current_team().set_countdown_time(new_time);
207 
208  recorder().add_countdown_update(new_time.count(), current_side());
209  }
210 
211  LOG_NG << "playmp::after_human_turn...";
212 
213  // Normal post-processing for human turns (clear undos, end the turn, etc.)
215 }
216 
218 {
219  LOG_NG << "is networked...";
220 
221  end_turn_enable(false);
222 
223  while(!should_return_to_play_side()) {
226  if(!mp_info_ || mp_info_->current_turn == turn()) {
227  skip_replay_ = false;
228  }
229  }
230 
232  }
233 
234  LOG_NG << "finished networked...";
235 }
236 
237 void playmp_controller::process_oos(const std::string& err_msg) const
238 {
239  // Notify the server of the oos error.
240  config cfg;
241  config& info = cfg.add_child("info");
242  info["type"] = "termination";
243  info["condition"] = "out of sync";
244  send_to_wesnothd(cfg);
245 
246  std::stringstream temp_buf;
247  std::vector<std::string> err_lines = utils::split(err_msg, '\n');
248  temp_buf << _("The game is out of sync, and cannot continue. There are a number of reasons this could happen: this "
249  "can occur if you or another player have modified their game settings. This may mean one of the "
250  "players is attempting to cheat. It could also be due to a bug in the game, but this is less "
251  "likely.\n\nDo you want to save an error log of your game?");
252 
253  if(!err_msg.empty()) {
254  temp_buf << " \n \n"; // and now the "Details:"
255  for(std::vector<std::string>::iterator i = err_lines.begin(); i != err_lines.end(); ++i) {
256  temp_buf << *i << '\n';
257  }
258  temp_buf << " \n";
259  }
260 
261  scoped_savegame_snapshot snapshot(*this);
263 
265 }
266 
267 void playmp_controller::handle_generic_event(const std::string& name)
268 {
270 }
272 {
273  return !mp_info_ || mp_info_->is_host;
274 }
275 
277 {
278  gui_->get_chat_manager().add_chat_message(std::time(nullptr), "", 0,
279  _("This side is in an idle state. To proceed with the game, it must be assigned to another controller. You may "
280  "use :droid, :control or :give_control for example."),
282 }
283 
285 {
286  if(replay_controller_.get() != nullptr) {
287  // We have probably been using the mp "back to turn" feature
288  // We continue play since we have reached the end of the replay.
289  replay_controller_.reset();
290  }
291 
292  // mouse_handler expects at least one team for linger mode to work.
293  send_actions();
294  assert(is_regular_game_end());
295  if(!get_end_level_data().transient.linger_mode || get_teams().empty() || video::headless()) {
296  if(!is_host() && gamestate().has_next_scenario()) {
297  // If we continue without lingering we need to
298  // make sure the host uploads the next scenario
299  // before we attempt to download it.
300  wait_for_upload();
301  }
302  } else {
303  linger();
304  }
305  end_turn_requested_ = true;
306 }
307 
309 {
310  undo_stack().clear();
312  send_actions();
313 }
314 
316 {
318  send_actions();
319 }
320 
321 
323 {
325  // receive chat during animations and delay
326  // But don't execute turn data during animations etc.
327  process_network_data(true);
328  // send_actions() makes sure that no actions that can
329  // still be undone is sent.
330  send_actions();
331  }
332 
334 }
335 
337 {
338  return mp_info_ != nullptr;
339 }
340 
341 void playmp_controller::send_to_wesnothd(const config& cfg, const std::string&) const
342 {
343  if(mp_info_ != nullptr) {
345  }
346 }
347 
349 {
350  if(mp_info_ != nullptr) {
351  return mp_info_->connection.receive_data(cfg);
352  } else {
353  return false;
354  }
355 }
356 
358 {
359  if(!recorder().at_end()) {
360  auto commandtype = get_replay_action_type(*recorder().peek_next_action());
361  // the only cases where process_network_data_impl puts something on the recorder
362  // without immidiately exceuting it are user choices
363  if(commandtype != REPLAY_ACTION_TYPE::DEPENDENT) {
364  ERR_NW << "attempting to process network data while still having data on the replay.";
365  }
366  return;
367  } else if (next_scenario_notified_) {
368  //Do nothing, Otherwise we might risk getting data that belongs to the next scenario.
369  return;
370  }
371 
372  config cfg;
373  // we could replace this "if" with a "while" to process multiple actions without delay between them
374  if(network_reader_.read(cfg)) {
375 
376  //Don't exceute the next turns actions.
377  unsync_only |= gamestate().in_phase(game_data::TURN_ENDED);
378  unsync_only |= is_regular_game_end();
379  unsync_only |= player_type_changed_;
380 
381  auto res = process_network_data_impl(cfg, unsync_only);
383  // chat_only=true, but we received a non-chat command, probably belonging to the next
384  // action when we are still exceuting the previous one.
385  // save the incoming data for later.
386  // Note: With this approach, incoming [turn] that we cannot handle also blocks other
387  // incoming data, like [change_controller].
388  network_reader_.push_front(std::move(cfg));
389  return;
390  }
392  end_turn();
393  }
394  }
395 }
396 
398 {
399  // the simple wesnothserver implementation in wesnoth was removed years ago.
400  assert(cfg.all_children_count() == 1);
401  assert(cfg.attribute_range().empty());
402  if(!recorder().at_end())
403  {
404  ERR_NW << "processing network data while still having data on the replay.";
405  }
406 
407  if (const auto message = cfg.optional_child("message"))
408  {
409  game_display::get_singleton()->get_chat_manager().add_chat_message(std::time(nullptr), message.value()["sender"], message.value()["side"].to_int(),
410  message.value()["message"], events::chat_handler::MESSAGE_PUBLIC,
412  }
413  else if (auto whisper = cfg.optional_child("whisper") /*&& is_observer()*/)
414  {
415  game_display::get_singleton()->get_chat_manager().add_chat_message(std::time(nullptr), "whisper: " + whisper["sender"].str(), 0,
416  whisper["message"], events::chat_handler::MESSAGE_PRIVATE,
417  prefs::get().message_bell());
418  }
419  else if (auto observer = cfg.optional_child("observer") )
420  {
422  }
423  else if (auto observer_quit = cfg.optional_child("observer_quit"))
424  {
426  }
427  else if (cfg.has_child("leave_game")) {
428  const bool has_reason = cfg.mandatory_child("leave_game").has_attribute("reason");
429  throw leavegame_wesnothd_error(has_reason ? cfg.mandatory_child("leave_game")["reason"].str() : "");
430  }
431  else if (auto turn = cfg.optional_child("turn"))
432  {
433  return process_network_turn_impl(*turn, chat_only);
434  }
435  else if (cfg.has_child("whiteboard"))
436  {
437  set_scontext_unsynced scontext;
438  get_whiteboard()->process_network_data(cfg);
439  }
440  else if (auto change = cfg.optional_child("change_controller"))
441  {
443  }
444  else if (auto side_drop_c = cfg.optional_child("side_drop"))
445  {
446  process_network_side_drop_impl(*side_drop_c);
447  }
448 
449  // The host has ended linger mode in a campaign -> enable the "End scenario" button
450  // and tell we did get the notification.
451  else if (cfg.has_child("notify_next_scenario")) {
453  }
454 
455  //If this client becomes the new host, notify the play_controller object about it
456  else if (cfg.has_child("host_transfer")) {
457  mp_info_->is_host = true;
458  if(is_linger_mode()) {
459  end_turn_enable(true);
460  }
461  }
462  else
463  {
464  ERR_NW << "found unknown command:\n" << cfg.debug();
465  }
466 
468 }
469 
471 {
472  //t can contain a [command] or a [upload_log]
473  assert(t.all_children_count() == 1);
474 
475  if(auto command = t.optional_child("command")) {
476  auto commandtype = get_replay_action_type(*command);
477  if(chat_only && (commandtype == REPLAY_ACTION_TYPE::SYNCED || commandtype == REPLAY_ACTION_TYPE::INVALID) ) {
479  }
480  if (commandtype == REPLAY_ACTION_TYPE::SYNCED && current_team().is_local()) {
481  // Executing those is better than OOS, also the server checks that other players don't send actions while it's not their turn.
482  ERR_NW << "Received a synced remote user action during our own turn";
483  }
484  }
485 
486  //note, that this function might call itself recursively: do_replay -> ... -> get_user_choice -> ... -> receive_actions -> ... -> handle_turn
488  do_replay();
490 }
491 
493 {
494  // Only the host receives this message when a player leaves/disconnects.
495  const int side_drop = side_drop_c["side_num"].to_int(0);
496  std::size_t index = side_drop -1;
497 
499 
500  if (index >= gamestate().board_.teams().size()) {
501  ERR_NW << "unknown side " << side_drop << " is dropping game";
502  throw ingame_wesnothd_error("");
503  }
504 
505  auto ctrl = side_controller::get_enum(side_drop_c["controller"].str());
506  if(!ctrl) {
507  ERR_NW << "unknown controller type issued from server on side drop: " << side_drop_c["controller"];
508  throw ingame_wesnothd_error("");
509  }
510 
511  if (ctrl == side_controller::type::ai) {
512  gamestate().board_.side_drop_to(side_drop, *ctrl);
513  return;
514  }
515  //null controlled side cannot be dropped because they aren't controlled by anyone.
516  else if (ctrl != side_controller::type::human) {
517  ERR_NW << "unknown controller type issued from server on side drop: " << side_controller::get_string(*ctrl);
518  throw ingame_wesnothd_error("");
519  }
520 
521  int action = 0;
522  int first_observer_option_idx = 0;
523  int control_change_options = 0;
524  bool has_next_scenario = gamestate().has_next_scenario();
525 
526  std::vector<std::string> observers;
527  std::vector<const team *> allies;
528  std::vector<std::string> options;
529 
530  const team &tm = gamestate().board_.teams()[index];
531 
532  for (const team &t : gamestate().board_.teams()) {
533  if (!t.is_enemy(side_drop) && !t.is_local_human() && !t.is_local_ai() && !t.is_network_ai() && !t.is_empty()
534  && t.current_player() != tm.current_player()) {
535  allies.push_back(&t);
536  }
537  }
538 
539  // We want to give host chance to decide what to do for side
540  if (!is_linger_mode() || has_next_scenario) {
541  utils::string_map t_vars;
542 
543  //get all allies in as options to transfer control
544  for (const team *t : allies) {
545  //if this is an ally of the dropping side and it is not us (choose local player
546  //if you want that) and not ai or empty and if it is not the dropping side itself,
547  //get this team in as well
548  t_vars["player"] = t->current_player();
549  options.emplace_back(VGETTEXT("Give control to their ally $player", t_vars));
550  control_change_options++;
551  }
552 
553  first_observer_option_idx = options.size();
554 
555  //get all observers in as options to transfer control
556  for (const std::string &screen_observers : game_display::get_singleton()->observers()) {
557  t_vars["player"] = screen_observers;
558  options.emplace_back(VGETTEXT("Give control to observer $player", t_vars));
559  observers.push_back(screen_observers);
560  control_change_options++;
561  }
562 
563  options.emplace_back(_("Replace with AI"));
564  options.emplace_back(_("Replace with local player"));
565  options.emplace_back(_("Set side to idle"));
566  options.emplace_back(_("Save and abort game"));
567 
568  t_vars["player"] = tm.current_player();
569  t_vars["side_drop"] = std::to_string(side_drop);
570  const std::string gettext_message = VGETTEXT("$player who controlled side $side_drop has left the game. What do you want to do?", t_vars);
571  gui2::dialogs::simple_item_selector dlg("", gettext_message, options);
572  dlg.set_single_button(true);
573  dlg.show();
574  action = dlg.selected_index();
575 
576  // If esc was pressed, default to setting side to idle
577  if (action == -1) {
578  action = control_change_options + 2;
579  }
580  } else {
581  // Always set leaving side to idle if in linger mode and there is no next scenario
582  action = 2;
583  }
584 
585  if (action < control_change_options) {
586  // Grant control to selected ally
587  // 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.
588  gamestate().board_.side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::idle);
589 
590  if (action < first_observer_option_idx) {
591  send_change_side_controller(side_drop, allies[action]->current_player());
592  } else {
593  send_change_side_controller(side_drop, observers[action - first_observer_option_idx]);
594  }
595  } else {
596  action -= control_change_options;
597 
598  //make the player an AI, and redo this turn, in case
599  //it was the current player's team who has just changed into
600  //an AI.
601  switch(action) {
602  case 0:
603  on_not_observer();
604  gamestate().board_.side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::ai);
605 
606  return;
607 
608  case 1:
609  on_not_observer();
610  gamestate().board_.side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::human);
611 
612  return;
613  case 2:
614  gamestate().board_.side_drop_to(side_drop, side_controller::type::human, side_proxy_controller::type::idle);
615 
616  return;
617 
618  case 3:
619  //The user pressed "end game". Don't throw a network error here or he will get
620  //thrown back to the title screen.
621  do_autosave();
623  default:
624  break;
625  }
626  }
627 }
628 
630 {
631 
632  if(change.empty()) {
633  ERR_NW << "Bad [change_controller] signal from server, [change_controller] tag was empty.";
634  return;
635  }
636 
637  const int side = change["side"].to_int();
638  const bool is_local = change["is_local"].to_bool();
639  const std::string player = change["player"];
640  const std::string controller_type = change["controller"];
641  const std::size_t index = side - 1;
642  if(index >= gamestate().board_.teams().size()) {
643  ERR_NW << "Bad [change_controller] signal from server, side out of bounds: " << change.debug();
644  return;
645  }
646 
647  const team & tm = gamestate().board_.teams().at(index);
648  const bool was_local = tm.is_local();
649 
650  gamestate().board_.side_change_controller(side, is_local, player, controller_type);
651 
652  if (!was_local && tm.is_local()) {
653  on_not_observer();
654  }
655 
657 
658  get_whiteboard()->on_change_controller(side,tm);
659 
660  player_type_changed_ |= game_display::get_singleton()->playing_team().side() == side && (was_local || tm.is_local());
661 }
662 
664 {
665  if(!is_networked_mp()) {
666  return;
667  }
668 
669  get_whiteboard()->send_network_data();
670 
671  const bool send_everything = synced_context::is_unsynced() ? !undo_stack().can_undo() : synced_context::undo_blocked();
672  const replay::DATA_TYPE data_type = send_everything ? replay::ALL_DATA : replay::NON_UNDO_DATA;
673 
674  config data = recorder().get_unsent_commands(data_type);
675  if (!data.empty()) {
676  send_to_wesnothd(config{ "turn", data});
677  }
678 }
679 
680 void playmp_controller::send_change_side_controller(int side, const std::string& player)
681 {
682  config cfg;
683  config& change = cfg.add_child("change_controller");
684  change["side"] = side;
685  change["player"] = player;
686  send_to_wesnothd(cfg);
687 }
double t
Definition: astarsearch.cpp:63
bool can_undo() const
True if there are actions that can be undone.
Definition: undo.hpp:99
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:199
void undo()
Undoes the top action on the undo stack.
Definition: undo.cpp:326
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
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:366
const_attr_itors attribute_range() const
Definition: config.cpp:760
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
std::size_t all_children_count() const
Definition: config.cpp:306
std::string debug() const
Definition: config.cpp:1240
bool empty() const
Definition: config.cpp:849
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:384
config & add_child(config_key_type key)
Definition: config.cpp:440
virtual void play_slice()
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)
const team & playing_team() const
Definition: display.cpp:337
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
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:223
void side_change_controller(int side_num, bool is_local, const std::string &pname, const std::string &controller_type)
Definition: game_board.cpp:239
@ TURN_PLAYING
The User is controlling the game and invoking actions The game can be saved here.
Definition: game_data.hpp:93
@ TURN_ENDED
The turn_end, side_turn_end etc [events] are fired next phase: TURN_STARTING_WAITING (default),...
Definition: game_data.hpp:96
static game_display * get_singleton()
display_chat_manager & get_chat_manager()
bool has_next_scenario() const
Checks whether this is not the last scenario (usually of a campaign)
Definition: game_state.cpp:393
bool in_phase(game_data::PHASE phase) const
Definition: game_state.hpp:109
game_board board_
Definition: game_state.hpp:44
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.
bool show(const unsigned auto_close_time=0)
Shows the window.
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.
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.
std::shared_ptr< wb::manager > get_whiteboard() const
bool is_replay() const
bool player_type_changed_
true when the controller of the currently playing side has changed.
std::size_t turn() const
const auto & timer() const
bool can_undo() const
replay & recorder() const
void process_network_data(bool unsync_only=false)
void send_actions() override
Sends replay [command]s to the server.
void process_oos(const std::string &err_msg) const override
Asks the user whether to continue on an OOS error.
void process_network_side_drop_impl(const config &t)
Handle incoming [side_drop] from the multiplayer server.
void send_change_side_controller(int side, const std::string &player)
Send [change_controller] to the multiplayer server.
mp_game_metadata * mp_info_
Information about our connection to the multiplayer server.
virtual bool is_host() const override
void process_network_change_controller_impl(const config &)
Handle incoming [change_controller] from the multiplayer server.
virtual void do_idle_notification() override
Will handle sending a networked notification in descendent classes.
void maybe_linger() override
playturn_network_adapter network_reader_
Helper to preprocess infoming network data.
bool receive_from_wesnothd(config &cfg) const override
void play_slice() 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
PROCESS_DATA_RESULT process_network_data_impl(const config &cfg, bool chat_only=false)
Check for and Handle incoming data from the multiplayer server.
virtual void on_not_observer() override
virtual void after_human_turn() override
bool is_networked_mp() const override
PROCESS_DATA_RESULT process_network_turn_impl(const config &t, bool chat_only=false)
Handle incoming [turn] from the multiplayer server.
@ CANNOT_HANDLE
when we couldn't handle the given action currently.
virtual void play_network_turn() override
Will handle networked turns in descendent classes.
void receive_actions() override
Reads and executes replay [command]s from the server.
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 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
virtual bool should_return_to_play_side() const override
void update_viewing_player() override
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 prefs & get()
bool message_bell()
static config get_update_shroud()
Records that the player has manually updated fog/shroud.
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:662
@ MARK_AS_SENT
Definition: replay.hpp:132
DATA_TYPE
Definition: replay.hpp:107
@ ALL_DATA
Definition: replay.hpp:107
@ NON_UNDO_DATA
Definition: replay.hpp:107
void add_surrender(int side_number)
Definition: replay.cpp:235
config get_unsent_commands(DATA_TYPE data_type)
Definition: replay.cpp:398
void add_countdown_update(int value, int team)
Definition: replay.cpp:241
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:60
bool save_game_interactive(const std::string &message, DIALOG_TYPE dialog_type)
Save a game interactively through the savegame dialog.
Definition: savegame.cpp:378
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()
static bool run_and_store(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
void set_action_bonus_count(const int count)
Definition: team.hpp:200
int side() const
Definition: team.hpp:175
void set_countdown_time(const std::chrono::milliseconds &amount) const
Definition: team.hpp:198
const std::string & current_player() const
Definition: team.hpp:220
bool is_local() const
Definition: team.hpp:247
int action_bonus_count() const
Definition: team.hpp:199
std::chrono::milliseconds countdown_time() const
Definition: team.hpp:197
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.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1028
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
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
const color_t NORMAL_COLOR
std::string observer
logger & info()
Definition: log.cpp:319
void turn_changed(const std::string &player_name)
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:70
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
bool headless()
The game is running headless.
Definition: video.cpp:139
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::string_view data
Definition: picture.cpp:178
static lg::log_domain log_engine("engine")
#define ERR_NW
static lg::log_domain log_network("network")
#define DBG_NG
#define LOG_NG
REPLAY_ACTION_TYPE get_replay_action_type(const config &command)
Definition: replay.cpp:692
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:707
void unblind()
Definition: display.hpp:979
We received invalid data from wesnothd during a game This means we cannot continue with the game but ...
wesnothd_connection & connection
unsigned current_turn
std::chrono::seconds mp_countdown_reservoir_time
std::chrono::seconds mp_countdown_action_bonus
std::chrono::seconds mp_countdown_turn_bonus
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
static constexpr utils::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.