The Battle for Wesnoth  1.17.14+dev
game.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
3  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 "server/wesnothd/game.hpp"
17 
18 #include "filesystem.hpp"
19 #include "game_config.hpp" // game_config::observer_team_name
20 #include "lexical_cast.hpp"
21 #include "log.hpp"
26 
27 #include <cstdio>
28 #include <iomanip>
29 #include <sstream>
30 
31 static lg::log_domain log_server("server");
32 #define ERR_GAME LOG_STREAM(err, log_server)
33 #define WRN_GAME LOG_STREAM(warn, log_server)
34 #define LOG_GAME LOG_STREAM(info, log_server)
35 #define DBG_GAME LOG_STREAM(debug, log_server)
36 
37 static lg::log_domain log_config("config");
38 #define WRN_CONFIG LOG_STREAM(warn, log_config)
39 
40 namespace
41 {
42 void split_conv_impl(std::vector<int>& res, const simple_wml::string_span& span)
43 {
44  if(!span.empty()) {
45  res.push_back(span.to_int());
46  }
47 }
48 
49 template<typename TResult, typename TConvert>
50 std::vector<TResult> split(const simple_wml::string_span& val, TConvert conv, const char c = ',')
51 {
52  std::vector<TResult> res;
53 
56 
57  while(i2 != val.end()) {
58  if(*i2 == c) {
59  conv(res, simple_wml::string_span(i1, i2));
60  ++i2;
61  i1 = i2;
62  } else {
63  ++i2;
64  }
65  }
66 
67  conv(res, simple_wml::string_span(i1, i2));
68  return res;
69 }
70 }
71 
72 namespace wesnothd
73 {
74 
75 int game::id_num = 1;
76 int game::db_id_num = 1;
77 
79  player_iterator host,
80  const std::string& name,
81  bool save_replays,
82  const std::string& replay_save_path)
83  : server(server)
84  , player_connections_(player_connections)
85  , id_(id_num++)
86  , db_id_(db_id_num++)
87  , name_(name)
88  , password_()
89  , owner_(host)
90  , players_()
91  , observers_()
92  , muted_observers_()
93  , sides_()
94  , side_controllers_()
95  , nsides_(0)
96  , started_(false)
97  , level_()
98  , history_()
99  , description_(nullptr)
100  , current_turn_(0)
101  , current_side_index_(0)
102  , num_turns_(0)
103  , all_observers_muted_(false)
104  , bans_()
105  , name_bans_()
106  , players_not_advanced_()
107  , termination_()
108  , save_replays_(save_replays)
109  , replay_save_path_(replay_save_path)
110  , rng_()
111  , last_choice_request_id_(-1) /* or maybe 0 ? it shouldn't matter*/
112 {
113  players_.push_back(owner_);
114 
115  // Mark the host as unavailable in the lobby.
116  owner_->info().mark_available(id_, name_);
117  owner_->info().set_status(player::PLAYING);
118 }
119 
121 {
122  try {
123  save_replay();
124 
125  for(player_iterator user_ptr : all_game_users()) {
126  remove_player(user_ptr, false, true);
127  }
128 
129  clear_history();
130  } catch(const boost::coroutines::detail::forced_unwind&) {
131  ERR_GAME << "Caught forced_unwind in game destructor!";
132  } catch(...) {
133  ERR_GAME << "Caught other exception in game destructor: " << utils::get_unknown_exception_type();
134  }
135 }
136 
137 /** returns const so that operator [] won't create empty keys if not existent */
139 {
140  if(const simple_wml::node* multiplayer = root.child("multiplayer")) {
141  return *multiplayer;
142  } else {
143  ERR_GAME << "no [multiplayer] found. Returning root";
144  return root;
145  }
146 }
147 
149 {
150  return get_multiplayer(level_.root())["observer"].to_bool(true);
151 }
152 
154 {
155  return std::find(observers_.begin(), observers_.end(), player) != observers_.end();
156 }
157 
159 {
160  if(!is_observer(player)) {
161  return false;
162  }
163 
165  return true;
166  }
167 
168  return std::find(muted_observers_.begin(), muted_observers_.end(), player) != muted_observers_.end();
169 }
170 
172 {
173  return std::find(players_.begin(), players_.end(), player) != players_.end();
174 }
175 
176 std::string game::username(player_iterator iter) const
177 {
178  return iter->info().name();
179 }
180 
181 std::string game::list_users(user_vector users) const
182 {
183  std::string list;
184 
185  for(auto user : users) {
186  if(!list.empty()) {
187  list += ", ";
188  }
189 
190  list += user->info().name();
191  }
192 
193  return list;
194 }
195 
197 {
199 
200  DBG_GAME << "****\n Performing controller tweaks. sides = ";
202  DBG_GAME << "****";
203 
204  update_side_data(); // Necessary to read the level_ and get sides_, etc. updated to match
205 
206  for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
207  simple_wml::node& side = *sides[side_index];
208 
209  if(side["controller"] != side_controller::none) {
210  if(!sides_[side_index]) {
211  sides_[side_index] = owner_;
212  std::stringstream msg;
213  msg << "Side " << side_index + 1
214  << " had no controller during controller tweaks! The host was assigned control.";
215 
216  LOG_GAME << msg.str() << " (game id: " << id_ << ", " << db_id_ << ")";
218  }
219 
220  std::string user_name = username(*sides_[side_index]);
221 
222  // Issue change_controller command, transferring this side to its owner with proper name and controller.
223  // Ensures that what the server now thinks is true is effected on all of the clients.
224  //
225  // In the server controller tweaks, we want to avoid sending controller change messages to the host.
226  // Doing this has the negative consequence that all of the AI side names are given the owners name.
227  // Therefore, if the side belongs to the host, we pass player_left = true, otherwise player_left = false.
228  change_controller(side_index, *sides_[side_index], user_name, sides_[side_index] == owner_);
229 
230  // next line change controller types found in level_ to be what is appropriate for an observer at game
231  // start.
232  side.set_attr("is_local", "no");
233 
234  if(!sides_[side_index]) {
235  std::stringstream msg;
236  msg << "Side " << side_index + 1 << " had no controller AFTER controller tweaks! Ruh Roh!";
237  LOG_GAME << msg.str() << " (game id: " << id_ << ", " << db_id_ << ")";
238  }
239  }
240  }
241 
242  // This is the last time that update_side_data will actually run, as now the game will start and
243  // started_ will be true.
245 
246  // TODO: Does it matter that the server is telling the host to change a bunch of sides?
247  // According to playturn.cpp, the host should ignore all such messages. Still might be better
248  // not to send them at all, although not if it complicates the server code.
249 }
250 
252 {
254  DBG_GAME << "****\n Starting game. sides = ";
256  DBG_GAME << "****";
257 
258  // If the game was already started we're actually advancing.
259  const bool advance = started_;
260  started_ = true;
261  // Prevent inserting empty keys when reading.
262  const simple_wml::node& multiplayer = get_multiplayer(level_.root());
263 
264  const bool save = multiplayer["savegame"].to_bool();
265  LOG_GAME
266  << starter->client_ip() << "\t" << starter->name() << "\t"
267  << (advance ? "advanced" : "started") << (save ? " reloaded" : "") << " game:\t\"" << name_ << "\" (" << id_
268  << ", " << db_id_ << ") with: " << list_users(players_)
269  << ". Settings: map: " << multiplayer["mp_scenario"]
270  << "\tera: " << multiplayer["mp_era"]
271  << "\tXP: " << multiplayer["experience_modifier"]
272  << "\tGPV: " << multiplayer["mp_village_gold"]
273  << "\tfog: " << multiplayer["mp_fog"]
274  << "\tshroud: " << multiplayer["mp_shroud"]
275  << "\tobservers: " << multiplayer["observer"]
276  << "\tshuffle: " << multiplayer["shuffle_sides"]
277  << "\ttimer: " << multiplayer["mp_countdown"]
278  << (multiplayer["mp_countdown"].to_bool()
279  ? "\treservoir time: " + multiplayer["mp_countdown_reservoir_time"].to_string()
280  + "\tinit time: " + multiplayer["mp_countdown_init_time"].to_string()
281  + "\taction bonus: " + multiplayer["mp_countdown_action_bonus"].to_string()
282  + "\tturn bonus: " + multiplayer["mp_countdown_turn_bonus"].to_string()
283  : "");
284 
285 
286  for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
287  simple_wml::node& side = *sides[side_index];
288 
289  if(side["controller"] != side_controller::none) {
290  if(side_index >= sides_.size()) {
291  continue;
292  }
293 
294  if(!sides_[side_index]) {
295  std::stringstream msg;
296  msg << "Side " << side_index + 1
297  << " has no controller but should! The host needs to assign control for the game to proceed past "
298  "that side's turn.";
299 
300  LOG_GAME << msg.str() << " (game id: " << id_ << ", " << db_id_ << ")";
302  }
303  }
304  }
305 
306  DBG_GAME << "Number of sides: " << nsides_;
307  int turn = 1;
308  int side = 0;
309 
310  // Savegames have a snapshot that tells us which side starts.
311  if(const simple_wml::node* snapshot = level_.root().child("snapshot")) {
312  turn = lexical_cast_default<int>((*snapshot)["turn_at"], 1);
313  side = lexical_cast_default<int>((*snapshot)["playing_team"], 0);
314  LOG_GAME << "Reload from turn: " << turn << ". Current side is: " << side + 1 << ".";
315  }
316  current_turn_ = turn;
317  current_side_index_ = side;
318  num_turns_ = lexical_cast_default<int>((*starting_pos(level_.root()))["turns"], -1);
319 
321  clear_history();
322 
323  // Send [observer] tags for all observers that are already in the game.
325 }
326 
328 {
329  const std::size_t side_index = (*side)["side"].to_int() - 1;
330 
331  // Negative values are casted (int -> std::size_t) to very high values to this check will fail for them too.
332  if(side_index >= sides_.size()) {
333  return false;
334  }
335 
336  if(sides_[side_index]) {
337  return false;
338  }
339 
340  // We expect that the host will really use our proposed side number. (He could do different...)
341  cfg.root().set_attr_dup("side", (*side)["side"]);
342 
343  // Tell the host which side the new player should take.
345  return true;
346 }
347 
349 {
350  DBG_GAME << "take_side...";
351 
352  if(started_) {
353  return false;
354  }
355 
357  cfg.root().set_attr_dup("name", user->name().c_str());
358 
359  // FIXME: The client code (multiplayer.wait.cpp) the host code (connect_engine.cpp) and the server code
360  // (this file) has this code to figure out a fitting side for new players, this is clearly too much.
361  // Check if we can figure out a fitting side.
363 
364  for(const simple_wml::node* side : sides) {
365  if(((*side)["controller"] == side_controller::human || (*side)["controller"] == side_controller::reserved)
366  && (*side)["current_player"] == user->name().c_str()) {
367 
368  if(send_taken_side(cfg, side)) {
369  return true;
370  }
371  }
372  }
373 
374  // If there was no fitting side just take the first available.
375  for(const simple_wml::node* side : sides) {
376  if((*side)["controller"] == side_controller::human) {
377  if(send_taken_side(cfg, side)) {
378  return true;
379  }
380  }
381  }
382 
383  DBG_GAME << "take_side: there are no more sides available";
384 
385  // If we get here we couldn't find a side to take
386  return false;
387 }
388 
390 {
391  side_controllers_.clear();
392  sides_.clear();
393 
394  nsides_ = get_sides_list().size();
395 
396  side_controllers_.resize(nsides_);
397  sides_.resize(nsides_);
398 }
399 
401 {
402  // Added by iceiceice: since level_ will now reflect how an observer views the replay start
403  // position and not the current position, the sides_, side_controllers_, players_ info should
404  // not be updated from the level_ after the game has started. Controller changes are now stored
405  // in the history, so an observer that joins will get up to date that way.
406  if(started_) {
407  return;
408  }
409 
410  DBG_GAME << "update_side_data...";
412 
413  // Remember everyone that is in the game.
414  const user_vector users = all_game_users();
415 
416  players_.clear();
417  observers_.clear();
418 
419  reset_sides();
420 
421  const simple_wml::node::child_list& level_sides = get_sides_list();
422 
423  // For each user:
424  // * Find the username.
425  // * Find the side this username corresponds to.
426  for(auto iter : users) {
427 
428  bool side_found = false;
429  for(unsigned side_index = 0; side_index < level_sides.size(); ++side_index) {
430  const simple_wml::node* side = level_sides[side_index];
431 
432  if(side_index >= sides_.size() || sides_[side_index]) {
433  continue;
434  }
435 
436  const simple_wml::string_span& player_id = (*side)["player_id"];
437  const simple_wml::string_span& controller = (*side)["controller"];
438 
439  auto type = side_controller::get_enum(controller.to_string());
440  if(player_id == iter->info().name().c_str()) {
441  // We found invalid [side] data. Some message would be cool.
442  if(controller != side_controller::human && controller != side_controller::ai) {
443  continue;
444  }
445 
446  if(type) {
447  side_controllers_[side_index] = *type;
448  }
449  sides_[side_index] = iter;
450  side_found = true;
451  } else if(iter == owner_ && controller == side_controller::none) {
452  // the *user == owner_ check has no effect,
453  // it's just an optimisation so that we only do this once.
454  if(type) {
455  side_controllers_[side_index] = *type;
456  }
457  }
458  }
459 
460  if(side_found) {
461  players_.push_back(iter);
462  iter->info().set_status(player::PLAYING);
463  } else {
464  observers_.push_back(iter);
465  iter->info().set_status(player::OBSERVING);
466  }
467  }
468 
470 }
471 
473 {
474  DBG_GAME << "transfer_side_control...";
475 
476  if(!is_player(player) && player != owner_) {
477  send_server_message("You cannot change controllers: not a player.", player);
478  return;
479  }
480 
481  // Check the side number.
482  const unsigned int side_num = cfg["side"].to_int();
483  if(side_num < 1 || side_num > sides_.size()) {
484  std::ostringstream msg;
485  msg << "The side number has to be between 1 and " << sides_.size() << ".";
486  send_server_message(msg.str(), player);
487  return;
488  }
489 
490  if(side_num > get_sides_list().size()) {
491  send_server_message("Invalid side number.", player);
492  return;
493  }
494 
495  const simple_wml::string_span& newplayer_name = cfg["player"];
496  auto old_player = sides_[side_num - 1];
497  const std::string& controller_type = cfg["to"].to_string();
498 
499  const std::string old_player_name = old_player ? username(*old_player) : "null";
500 
501  // Not supported anymore.
502  if(newplayer_name.empty()) {
503  std::stringstream msg;
504  msg << "Received invalid [change_controller] with no player= attribute specified";
505  DBG_GAME << msg.str();
506  send_server_message(msg.str(), player);
507  return;
508  }
509 
510  // Check if the sender actually owns the side he gives away or is the host.
511  if(!(player == old_player || player == owner_)) {
512  std::stringstream msg;
513  msg << "You can't give away side " << side_num << ". It's controlled by '" << old_player_name << "' not you.";
514  DBG_GAME << msg.str();
515  send_server_message(msg.str(), player);
516  return;
517  }
518 
519  // find the player that is passed control
520  auto newplayer { find_user(newplayer_name) };
521 
522  // Is he in this game?
523  if(!newplayer || !is_member(*newplayer)) {
524  send_server_message(newplayer_name.to_string() + " is not in this game", player);
525  return;
526  }
527 
528  if(newplayer == old_player) {
529  // if the player is unchanged and the controller type (human or ai) is also unchanged then nothing to do
530  // else only need to change the controller type rather than the player who controls the side
531  // :droid provides a valid controller_type; :control provides nothing since it's only tranferring control between players regardless of type
532  auto type = side_controller::get_enum(controller_type);
533  if(!type || type == side_controllers_[side_num - 1]) {
534  std::stringstream msg;
535  msg << "Side " << side_num << " is already controlled by " << newplayer_name << ".";
536  send_server_message(msg.str(), player);
537  return;
538  } else {
539  side_controllers_[side_num - 1] = *side_controller::get_enum(controller_type);
540  change_controller_type(side_num - 1, *newplayer, (*newplayer)->info().name());
541  return;
542  }
543  }
544 
545  sides_[side_num - 1].reset();
546 
547  // If the old player lost his last side, make him an observer.
548  if(old_player && std::find(sides_.begin(), sides_.end(), old_player) == sides_.end() && is_player(*old_player)) {
549  observers_.push_back(*old_player);
550 
551  (*old_player)->info().set_status(player::OBSERVING);
552  players_.erase(std::remove(players_.begin(), players_.end(), old_player), players_.end());
553 
554  // Tell others that the player becomes an observer.
555  send_and_record_server_message(old_player_name + " becomes an observer.");
556 
557  // Update the client side observer list for everyone except old player.
558  simple_wml::document observer_join;
559  observer_join.root().add_child("observer").set_attr_dup("name", old_player_name.c_str());
560  send_data(observer_join, *old_player);
561  }
562 
563  change_controller(side_num - 1, *newplayer, (*newplayer)->info().name(), false);
564 
565  // If we gave the new side to an observer add him to players_.
566  if(is_observer(*newplayer)) {
567  players_.push_back(*newplayer);
568  (*newplayer)->info().set_status(player::PLAYING);
569  observers_.erase(std::remove(observers_.begin(), observers_.end(), newplayer), observers_.end());
570  // Send everyone but the new player the observer_quit message.
571  send_observerquit(*newplayer);
572  }
573 }
574 
576  const std::size_t side_index, player_iterator player, const std::string& player_name, const bool player_left)
577 {
578  DBG_GAME << __func__ << "...";
579 
580  const std::string& side = lexical_cast_default<std::string, std::size_t>(side_index + 1);
581  sides_[side_index] = player;
582 
583  if(player_left && side_controllers_[side_index] == side_controller::type::ai) {
584  // Automatic AI side transfer.
585  } else {
586  if(started_) {
587  send_and_record_server_message(player_name + " takes control of side " + side + ".");
588  }
589  }
590 
591  auto response = change_controller_type(side_index, player, player_name);
592 
593  if(started_) {
594  // the purpose of these records is so that observers, replay viewers, etc get controller updates correctly
595  record_data(response->clone());
596  }
597 
598  // Tell the new player that he controls this side now.
599  // Just don't send it when the player left the game. (The host gets the
600  // side_drop already.)
601  if(!player_left) {
602  response->root().child("change_controller")->set_attr("is_local", "yes");
603  server.send_to_player(player, *response.get());
604  }
605 }
606 
607 std::unique_ptr<simple_wml::document> game::change_controller_type(const std::size_t side_index, player_iterator player, const std::string& player_name)
608 {
609  const std::string& side = std::to_string(side_index + 1);
610  simple_wml::document response;
611  simple_wml::node& change = response.root().add_child("change_controller");
612 
613  change.set_attr_dup("side", side.c_str());
614  change.set_attr_dup("player", player_name.c_str());
615 
616  change.set_attr_dup("controller", side_controller::get_string(side_controllers_[side_index]).c_str());
617  change.set_attr("is_local", "no");
618 
619  send_data(response, player);
620  return response.clone();
621 }
622 
624 {
625  const std::string owner_name = username(owner_);
627  cfg.root().add_child("host_transfer");
628 
629  std::string message = owner_name + " has been chosen as the new host.";
632 }
633 
635 {
636  if(started_ || description_ == nullptr) {
637  return false;
638  }
639 
640  int available_slots = 0;
641  int num_sides = get_sides_list().size();
642  int i = 0;
643 
644  for(const simple_wml::node* side : get_sides_list()) {
645  if(((*side)["allow_player"].to_bool(true) == false) || (*side)["controller"] == side_controller::none) {
646  num_sides--;
647  } else if(!sides_[i]) {
648  ++available_slots;
649  }
650 
651  ++i;
652  }
653 
654  simple_wml::node* slots_cfg = description_->child("slot_data");
655  if(!slots_cfg) {
656  slots_cfg = &description_->add_child("slot_data");
657  }
658 
659  slots_cfg->set_attr_int("vacant", available_slots);
660  slots_cfg->set_attr_int("max", num_sides);
661 
662  return true;
663 }
664 
665 bool game::player_is_banned(player_iterator player, const std::string& name) const
666 {
667  auto ban = std::find(bans_.begin(), bans_.end(), player->client_ip());
668  auto name_ban = std::find(name_bans_.begin(), name_bans_.end(), name);
669 
670  return ban != bans_.end() || name_ban != name_bans_.end();
671 }
672 
674 {
677  send_and_record_server_message("All observers have been muted.");
678  } else {
679  send_and_record_server_message("Muting of all observers has been removed.");
680  }
681 }
682 
684 {
686  send_server_message("All observers are muted.", user);
687  return;
688  }
689 
690  std::string muted_nicks = list_users(muted_observers_);
691 
692  send_server_message("Muted observers: " + muted_nicks, user);
693 }
694 
696 {
697  if(muter != owner_) {
698  send_server_message("You cannot mute: not the game host.", muter);
699  return;
700  }
701 
702  const simple_wml::string_span& username = mute["username"];
703  if(username.empty()) {
704  send_muted_observers(muter);
705  return;
706  }
707 
708  auto user { find_user(username) };
709 
710  /*
711  * @todo FIXME: Maybe rather save muted nicks as a set of strings and also allow muting of usernames not in the game.
712  */
713  if(!user || !is_observer(*user)) {
714  send_server_message("Observer '" + username.to_string() + "' not found.", muter);
715  return;
716  }
717 
718  // Prevent muting ourselves.
719  if(user == muter) {
720  send_server_message("Don't mute yourself, silly.", muter);
721  return;
722  }
723 
724  if(is_muted_observer(*user)) {
725  send_server_message(username.to_string() + " is already muted.", muter);
726  return;
727  }
728 
729  LOG_GAME << muter->client_ip() << "\t" << game::username(muter) << " muted: " << username << " ("
730  << (*user)->client_ip() << ")\tin game:\t\"" << name_ << "\" (" << id_ << ", " << db_id_ << ")";
731 
732  muted_observers_.push_back(*user);
733  send_and_record_server_message(username.to_string() + " has been muted.");
734 }
735 
737 {
738  if(unmuter != owner_) {
739  send_server_message("You cannot unmute: not the game host.", unmuter);
740  return;
741  }
742 
743  const simple_wml::string_span& username = unmute["username"];
744  if(username.empty()) {
745  muted_observers_.clear();
746  send_and_record_server_message("Everyone has been unmuted.");
747  return;
748  }
749 
750  auto user { find_user(username) };
751  if(!user || !is_observer(*user)) {
752  send_server_message("Observer '" + username.to_string() + "' not found.", unmuter);
753  return;
754  }
755 
756  if(!is_muted_observer(*user)) {
757  send_server_message(username.to_string() + " is not muted.", unmuter);
758  return;
759  }
760 
761  LOG_GAME << unmuter->client_ip() << "\t" << game::username(unmuter) << " unmuted: " << username << " ("
762  << (*user)->client_ip() << ")\tin game:\t\"" << name_ << "\" (" << id_ << ", " << db_id_ << ")";
763 
765  send_and_record_server_message(username.to_string() + " has been unmuted.");
766 }
767 
769 {
770  static simple_wml::document leave_game("[leave_game]\n[/leave_game]\n", simple_wml::INIT_COMPRESSED);
771  server.send_to_player(user, leave_game);
772 }
773 
774 std::optional<player_iterator> game::kick_member(const simple_wml::node& kick, player_iterator kicker)
775 {
776  if(kicker != owner_) {
777  send_server_message("You cannot kick: not the game host", kicker);
778  return {};
779  }
780 
781  const simple_wml::string_span& username = kick["username"];
782  auto user { find_user(username) };
783 
784  if(!user || !is_member(*user)) {
785  send_server_message("'" + username.to_string() + "' is not a member of this game.", kicker);
786  return {};
787  } else if(user == kicker) {
788  send_server_message("Don't kick yourself, silly.", kicker);
789  return {};
790  } else if((*user)->info().is_moderator()) {
791  send_server_message("You're not allowed to kick a moderator.", kicker);
792  return {};
793  }
794 
795  LOG_GAME << kicker->client_ip() << "\t" << game::username(kicker) << "\tkicked: " << username << " ("
796  << (*user)->client_ip() << ")\tfrom game:\t\"" << name_ << "\" (" << id_ << ", " << db_id_ << ")";
797 
798  send_and_record_server_message(username.to_string() + " has been kicked.");
799 
800  // Tell the user to leave the game.
801  send_leave_game(*user);
802  remove_player(*user);
803  return user;
804 }
805 
806 std::optional<player_iterator> game::ban_user(const simple_wml::node& ban, player_iterator banner)
807 {
808  if(banner != owner_) {
809  send_server_message("You cannot ban: not the game host", banner);
810  return {};
811  }
812 
813  const simple_wml::string_span& username = ban["username"];
814  auto user { find_user(username) };
815 
816  if(!user) {
817  send_server_message("User '" + username.to_string() + "' not found.", banner);
818  return {};
819  } else if(user == banner) {
820  send_server_message("Don't ban yourself, silly.", banner);
821  return {};
822  } else if(player_is_banned(*user, username.to_string())) {
823  send_server_message("'" + username.to_string() + "' is already banned.", banner);
824  return {};
825  } else if((*user)->info().is_moderator()) {
826  send_server_message("You're not allowed to ban a moderator.", banner);
827  return {};
828  }
829 
830  LOG_GAME << banner->client_ip() << "\t" << game::username(banner) << "\tbanned: " << username << " ("
831  << (*user)->client_ip() << ")\tfrom game:\t\"" << name_ << "\" (" << id_ << ", " << db_id_ << ")";
832 
833  bans_.push_back((*user)->client_ip());
834  name_bans_.push_back(username.to_string());
835  send_and_record_server_message(username.to_string() + " has been banned.");
836 
837  if(is_member(*user)) {
838  // tell the user to leave the game.
839  send_leave_game(*user);
840  remove_player(*user);
841  return user;
842  }
843 
844  // Don't return the user if he wasn't in this game.
845  return {};
846 }
847 
849 {
850  if(unbanner != owner_) {
851  send_server_message("You cannot unban: not the game host.", unbanner);
852  return;
853  }
854 
855  const simple_wml::string_span& username = unban["username"];
856  auto user { find_user(username) };
857 
858  if(!user) {
859  send_server_message("User '" + username.to_string() + "' not found.", unbanner);
860  return;
861  }
862 
863  if(!player_is_banned(*user, username.to_string())) {
864  send_server_message("'" + username.to_string() + "' is not banned.", unbanner);
865  return;
866  }
867 
868  LOG_GAME
869  << unbanner->client_ip() << "\t" << unbanner->info().name()
870  << "\tunbanned: " << username << " (" << (*user)->client_ip() << ")\tfrom game:\t\"" << name_ << "\" ("
871  << id_ << ", " << db_id_ << ")";
872 
873  bans_.erase(std::remove(bans_.begin(), bans_.end(), (*user)->client_ip()), bans_.end());
874  name_bans_.erase(std::remove(name_bans_.begin(), name_bans_.end(), username.to_string()), name_bans_.end());
875  send_and_record_server_message(username.to_string() + " has been unbanned.");
876 }
877 
879 {
880  simple_wml::node* const message = data.root().child("message");
881  assert(message);
882  message->set_attr_dup("sender", user->info().name().c_str());
883 
884  const simple_wml::string_span& msg = (*message)["message"];
885  chat_message::truncate_message(msg, *message);
886 
887  send_data(data, user);
888 }
889 
891 {
892  const bool is_player = this->is_player(user);
893  const bool is_host = user == owner_;
894  const bool is_current = is_current_player(user);
895 
896  if(command.has_attr("from_side")) {
897  const std::size_t from_side_index = command["from_side"].to_int() - 1;
898 
899  // Someone pretends to be the server...
900  if(command["from_side"] == "server") {
901  return false;
902  }
903 
904  if(from_side_index >= sides_.size() || sides_[from_side_index] != user) {
905  return false;
906  }
907  }
908 
909  if(is_current) {
910  return true;
911  }
912 
913  // Only single commands allowed.
914  // NOTE: some non-dependent commands like move,attack.. might contain a [checkup] tag after their first data.
915  // But those packages are only sent by the currently active player which we check above.
916  if(!command.one_child()) {
917  return false;
918  }
919 
920  // Chatting is never an illegal command.
921  if(command.child("speak")) {
922  return true;
923  }
924  if(command.child("surrender")) {
925  const simple_wml::string_span& sn = command.child("surrender")->attr("side_number");
926  if(sn.is_null()) {
927  return false;
928  }
929 
930  std::size_t side_number = sn.to_int();
931  if(side_number >= sides_.size() || sides_[side_number] != user) {
932  return false;
933  } else {
934  return true;
935  }
936  }
937 
938  // AKA it's generated by get_user_input for example [global_variable]
939  if(is_player && command.has_attr("dependent") && command.has_attr("from_side")) {
940  return true;
941  }
942 
943  if((is_player || is_host) && (
944  command.child("label") ||
945  command.child("clear_labels") ||
946  command.child("rename") ||
947  command.child("countdown_update")
948  )) {
949  return true;
950  }
951 
952  return false;
953 }
954 
956 {
957  // DBG_GAME << "processing commands: '" << cfg << "'";
958  if(!started_) {
959  return false;
960  }
961 
962  simple_wml::node* const turn = data.root().child("turn");
963  bool turn_ended = false;
964 
965  // Any private 'speak' commands must be repackaged separate
966  // to other commands, and re-sent, since they should only go
967  // to some clients.
968  bool repackage = false;
969  int index = 0;
970  std::vector<int> marked;
971 
972  const simple_wml::node::child_list& commands = turn->children("command");
973 
974  for(simple_wml::node* command : commands) {
975  DBG_GAME << "game " << id_ << ", " << db_id_ << " received [" << (*command).first_child() << "] from player '" << username(user)
976  << "'(" << ") during turn " << current_side_index_ + 1 << "," << current_turn_;
977  if(!is_legal_command(*command, user)) {
978  LOG_GAME << "ILLEGAL COMMAND in game: " << id_ << ", " << db_id_ << " (((" << simple_wml::node_to_string(*command)
979  << ")))";
980 
981  std::stringstream msg;
982  msg << "Removing illegal command '" << (*command).first_child().to_string() << "' from: " << username(user)
983  << ". Current player is: " << (current_player() ? username(*current_player()) : "<none>") << " (" << current_side_index_ + 1 << "/" << nsides_
984  << ").";
985  LOG_GAME << msg.str() << " (game id: " << id_ << ", " << db_id_ << ")";
987 
988  marked.push_back(index - marked.size());
989  } else if((*command).child("speak")) {
990  simple_wml::node& speak = *(*command).child("speak");
991  if(!speak["to_sides"].empty() || is_muted_observer(user)) {
992  DBG_GAME << "repackaging...";
993  repackage = true;
994  }
995 
996  const simple_wml::string_span& msg = speak["message"];
997  chat_message::truncate_message(msg, speak);
998 
999  // Force the description to be correct,
1000  // to prevent spoofing of messages.
1001  speak.set_attr_dup("id", user->info().name().c_str());
1002 
1003  // Also check the side for players.
1004  if(is_player(user)) {
1005  const std::size_t side_index = speak["side"].to_int() - 1;
1006 
1007  if(side_index >= sides_.size() || sides_[side_index] != user) {
1008  if(user == current_player()) {
1009  speak.set_attr_dup("side", lexical_cast_default<std::string>(current_side() + 1).c_str());
1010  } else {
1011  const auto s = std::find(sides_.begin(), sides_.end(), user);
1012  speak.set_attr_dup("side", lexical_cast_default<std::string>(s - sides_.begin() + 1).c_str());
1013  }
1014  }
1015  }
1016  } else if (command->child("surrender")) {
1017  std::size_t side_index = 0;
1018 
1019  for(auto s : sides_) {
1020  if(s == user) {
1021  break;
1022  }
1023  ++side_index;
1024  }
1025 
1026  if(side_index < sides_.size()) {
1028  std::string playername;
1029  cfg.root().set_attr_dup("side", std::to_string(side_index + 1).c_str());
1030 
1031  // figure out who gets the surrendered side
1032  if(owner_ == user) {
1033  auto new_side_index = (side_index + 1) % sides_.size();
1034  auto new_owner = sides_[new_side_index];
1035  while(!new_owner) {
1036  new_side_index = (new_side_index + 1) % sides_.size();
1037  if(new_side_index == side_index) {
1038  ERR_GAME << "Ran out of sides to surrender to.";
1039  return false;
1040  }
1041  new_owner = sides_[new_side_index];
1042  }
1043  playername = username(*new_owner);
1044  } else {
1045  playername = username(owner_);
1046  }
1047 
1048  cfg.root().set_attr_dup("player", playername.c_str());
1049  transfer_side_control(user, cfg.root());
1050  }
1051  send_and_record_server_message(username(user) + " has surrendered.");
1052  } else if(is_current_player(user) && (*command).child("end_turn")) {
1053  simple_wml::node& endturn = *(*command).child("end_turn");
1054  turn_ended = end_turn(endturn["next_player_number"].to_int());
1055  }
1056 
1057  ++index;
1058  }
1059 
1060  for(const int j : marked) {
1061  turn->remove_child("command", j);
1062  }
1063 
1064  if(turn->no_children()) {
1065  return false;
1066  }
1067 
1068  if(!repackage) {
1069  record_data(data.clone());
1070  send_data(data, user);
1071  return turn_ended;
1072  }
1073 
1074  for(simple_wml::node* command : commands) {
1075  simple_wml::node* const speak = (*command).child("speak");
1076  if(speak == nullptr) {
1077  auto mdata = std::make_unique<simple_wml::document>();
1078  simple_wml::node& mturn = mdata->root().add_child("turn");
1079  (*command).copy_into(mturn.add_child("command"));
1080  send_data(*mdata, user);
1081  record_data(std::move(mdata));
1082  continue;
1083  }
1084 
1085  const simple_wml::string_span& to_sides = (*speak)["to_sides"];
1086 
1087  // Anyone can send to the observer team.
1088  if(is_muted_observer(user) && to_sides != game_config::observer_team_name.c_str()) {
1089  send_server_message("You have been muted, others can't see your message!", user);
1090  continue;
1091  }
1092 
1093  auto message = std::make_unique<simple_wml::document>();
1094  simple_wml::node& message_turn = message->root().add_child("turn");
1095  simple_wml::node& message_turn_command = message_turn.add_child("command");
1096  message_turn_command.set_attr("undo", "no");
1097  speak->copy_into(message_turn_command.add_child("speak"));
1098 
1099  if(to_sides.empty()) {
1100  send_data(*message, user);
1101  record_data(std::move(message));
1102  } else if(to_sides == game_config::observer_team_name) {
1103  send_to_players(*message, observers_, user);
1104  record_data(std::move(message));
1105  } else {
1106  send_data_sides(*message, to_sides, user);
1107  }
1108  }
1109 
1110  return turn_ended;
1111 }
1112 
1114 {
1115  uint32_t seed = rng_.get_next_random();
1116 
1117  std::stringstream stream;
1118  stream << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex << seed;
1119 
1120  auto mdata = std::make_unique<simple_wml::document>();
1121  simple_wml::node& turn = mdata->root().add_child("turn");
1122  simple_wml::node& command = turn.add_child("command");
1123  simple_wml::node& random_seed = command.add_child("random_seed");
1124 
1125  random_seed.set_attr_dup("new_seed", stream.str().c_str());
1126 
1127  command.set_attr("from_side", "server");
1128  command.set_attr("dependent", "yes");
1129 
1130  send_data(*mdata, {});
1131  record_data(std::move(mdata));
1132 }
1133 
1135 {
1136  ++nsides_;
1137  side_controllers_.push_back(side_controller::type::none);
1138  sides_.emplace_back();
1139 }
1140 
1142 {
1143  const std::size_t side_index = req["side"].to_int() - 1;
1144  auto new_controller = side_controller::get_enum(req["new_controller"].to_string());
1145  auto old_controller = side_controller::get_enum(req["old_controller"].to_string());
1146 
1147  if(!new_controller) {
1149  "Could not handle [request_choice] [change_controller] with invalid controller '" + req["new_controller"].to_string() + "'");
1150  return;
1151  }
1152 
1153  if(!old_controller) {
1155  "Could not handle [request_choice] [change_controller] with invalid controller '" + req["old_controller"].to_string() + "'");
1156  return;
1157  }
1158 
1159  if(old_controller != this->side_controllers_[side_index]) {
1161  "Found unexpected old_controller= '" + side_controller::get_string(*old_controller) + "' in [request_choice] [change_controller]");
1162  }
1163 
1164  if(side_index >= sides_.size()) {
1166  "Could not handle [request_choice] [change_controller] with invalid side '" + req["side"].to_string() + "'");
1167  return;
1168  }
1169 
1170  const bool was_null = this->side_controllers_[side_index] == side_controller::type::none;
1171  const bool becomes_null = new_controller == side_controller::type::none;
1172 
1173  if(was_null) {
1174  assert(!sides_[side_index]);
1175  sides_[side_index] = current_player();
1176  }
1177 
1178  if(becomes_null) {
1179  sides_[side_index].reset();
1180  }
1181 
1182  side_controllers_[side_index] = *new_controller;
1183 
1184  auto mdata = std::make_unique<simple_wml::document>();
1185  simple_wml::node& turn = mdata->root().add_child("turn");
1186  simple_wml::node& command = turn.add_child("command");
1187  simple_wml::node& change_controller_wml = command.add_child("change_controller_wml");
1188 
1189  change_controller_wml.set_attr_dup("controller", side_controller::get_string(*new_controller).c_str());
1190  change_controller_wml.set_attr("is_local", "yes");
1191 
1192  command.set_attr("from_side", "server");
1193  command.set_attr("dependent", "yes");
1194 
1195  if(sides_[side_index]) {
1196  server.send_to_player((*sides_[side_index]), *mdata);
1197  }
1198 
1199  change_controller_wml.set_attr("is_local", "no");
1200 
1201  send_data(*mdata, sides_[side_index]);
1202  record_data(std::move(mdata));
1203 }
1204 
1206 {
1207 
1208  if(!started_) {
1209  return;
1210  }
1211 
1212  // note, that during end turn events, it's side=1 for the server but side= side_count() on the clients.
1213 
1214  // Otherwise we allow observers to cause OOS for the playing clients by sending
1215  // server choice requests based on incompatible local changes. To solve this we block
1216  // server choice requests from observers.
1217  if(user != owner_ && !is_player(user)) {
1218  return;
1219  }
1220 
1221  // since we reset the last_choice_request_id_ when a new scenario is loaded,
1222  // the code would otherwise wrongly accept these requests from client in old
1223  // scenarios. which would result on oos.
1224  if(players_not_advanced_.find(&*user) != players_not_advanced_.end()) {
1225  return;
1226  }
1227 
1228  int request_id = lexical_cast_default<int>(data["request_id"], -10);
1229  if(request_id <= last_choice_request_id_) {
1230  // We gave already an anwer to this request.
1231  return;
1232  }
1233 
1234  DBG_GAME << "answering choice request " << request_id << " by player "
1235  << user->info().name();
1236  last_choice_request_id_ = request_id;
1237 
1238  if(data.child("random_seed")) {
1240  } else if(const simple_wml::node* ccw = data.child("change_controller_wml")) {
1242  } else if(data.child("add_side_wml")) {
1244  } else {
1245  send_and_record_server_message("Found unknown server choice request: [" + data.first_child().to_string() + "]");
1246  }
1247 }
1248 
1250 {
1251  if(!started_ || !is_player(user)) {
1252  return;
1253  }
1254 
1255  const simple_wml::node& wb_node = *data.child("whiteboard");
1256 
1257  // Ensure "side" attribute match with user
1258  const simple_wml::string_span& to_sides = wb_node["to_sides"];
1259  std::size_t const side_index = wb_node["side"].to_int() - 1;
1260 
1261  if(side_index >= sides_.size() || sides_[side_index] != user) {
1262  std::ostringstream msg;
1263  msg << "Ignoring illegal whiteboard data, sent from user '" << user->info().name()
1264  << "' which had an invalid side '" << side_index + 1 << "' specified" << std::endl;
1265 
1266  const std::string& msg_str = msg.str();
1267 
1268  LOG_GAME << msg_str;
1270  return;
1271  }
1272 
1273  send_data_sides(data, to_sides, user);
1274 }
1275 
1277 {
1278  if(!started_ || !is_player(user)) {
1279  return;
1280  }
1281 
1282  const simple_wml::node& ctw_node = *data.child("change_turns_wml");
1283  const int current_turn = ctw_node["current"].to_int();
1284  const int num_turns = ctw_node["max"].to_int();
1285  if(num_turns > 10000 || current_turn > 10000) {
1286  // ignore this to prevent errors related to integer overflow.
1287  return;
1288  }
1289 
1291  num_turns_ = num_turns;
1292 
1293  assert(static_cast<int>(this->current_turn()) == current_turn);
1294 
1295  simple_wml::node* turns_cfg = description_->child("turn_data");
1296  if(!turns_cfg) {
1297  turns_cfg = &description_->add_child("turn_data");
1298  }
1299 
1300  ctw_node.copy_into(*turns_cfg);
1301 
1302  // Don't send or store this change, all players should have gotten it by wml.
1303 }
1304 
1305 bool game::end_turn(int new_side)
1306 {
1307  if(new_side > 0) {
1308  current_side_index_ = new_side - 1;
1309  }
1310  else {
1312  }
1313 
1314  // Skip over empty sides.
1315  for(int i = 0; i < nsides_ && side_controllers_[current_side()] == side_controller::type::none; ++i) {
1317  }
1318 
1319  auto res = std::div(current_side_index_, nsides_ > 0 ? nsides_ : 1);
1320 
1321  if(res.quot == 0) {
1322  return false;
1323  }
1324  current_side_index_ = res.rem;
1325  current_turn_ += res.quot;
1326 
1327  if(description_ == nullptr) {
1328  // TODO: why do we need this?
1329  return false;
1330  }
1331 
1332  update_turn_data();
1333 
1334  return true;
1335 }
1336 
1338 {
1339  if(description_ == nullptr) {
1340  return;
1341  }
1342 
1343  simple_wml::node* turns_cfg = description_->child("turn_data");
1344  if(!turns_cfg) {
1345  turns_cfg = &description_->add_child("turn_data");
1346  }
1347 
1348  turns_cfg->set_attr_int("current", current_turn());
1349  turns_cfg->set_attr_int("max", num_turns_);
1350 }
1351 
1353 {
1354  if(is_member(player)) {
1355  ERR_GAME << "ERROR: Player is already in this game.";
1356  return false;
1357  }
1358 
1359  auto user = player;
1360 
1362 
1363  bool became_observer = false;
1364  if(!started_ && !observer && take_side(user)) {
1365  DBG_GAME << "adding player...";
1366  players_.push_back(player);
1367 
1368  user->info().set_status(player::PLAYING);
1369 
1370  send_and_record_server_message(user->info().name() + " has joined the game.", player);
1371  } else if(!allow_observers() && !user->info().is_moderator()) {
1372  return false;
1373  } else {
1374  if(!observer) {
1375  became_observer = true;
1376  observer = true;
1377  }
1378 
1379  DBG_GAME << "adding observer...";
1380  observers_.push_back(player);
1381  if(!allow_observers()) {
1383  user->info().name() + " is now observing the game.", player);
1384  }
1385 
1386  simple_wml::document observer_join;
1387  observer_join.root()
1388  .add_child("observer")
1389  .set_attr_dup("name", user->info().name().c_str());
1390 
1391  // Send observer join to everyone except the new observer.
1392  send_data(observer_join, player);
1393  }
1394 
1395  LOG_GAME
1396  << player->client_ip() << "\t" << user->info().name() << "\tjoined game:\t\""
1397  << name_ << "\" (" << id_ << ", " << db_id_ << ")" << (observer ? " as an observer" : "") << ".";
1398 
1399  user->info().mark_available(id_, name_);
1400  user->info().set_status((observer) ? player::OBSERVING : player::PLAYING);
1402 
1403  // Send the user the game data.
1404  server.send_to_player(player, level_);
1405 
1406  if(started_) {
1407  // Tell this player that the game has started
1408  static simple_wml::document start_game_doc("[start_game]\n[/start_game]\n", simple_wml::INIT_COMPRESSED);
1409  server.send_to_player(player, start_game_doc);
1410 
1411  // Send observer join of all the observers in the game to the new player
1412  // only once the game started. The client forgets about it anyway otherwise.
1413  send_observerjoins(player);
1414 
1415  // Send the player the history of the game to-date.
1416  send_history(player);
1417  } else {
1418  send_user_list();
1419  }
1420 
1421  const std::string clones = has_same_ip(player);
1422  if(!clones.empty()) {
1424  user->info().name() + " has the same IP as: " + clones);
1425  }
1426 
1427  if(became_observer) {
1428  // in case someone took the last slot right before this player
1429  send_server_message("You are an observer.", player);
1430  }
1431 
1432  return true;
1433 }
1434 
1435 bool game::remove_player(player_iterator player, const bool disconnect, const bool destruct)
1436 {
1437  if(!is_member(player)) {
1438  ERR_GAME << "ERROR: User is not in this game.";
1439  return false;
1440  }
1441 
1443  DBG_GAME << "removing player...";
1444 
1445  const bool host = (player == owner_);
1446  const bool observer = is_observer(player);
1447 
1448  players_.erase(std::remove(players_.begin(), players_.end(), player), players_.end());
1449  observers_.erase(std::remove(observers_.begin(), observers_.end(), player), observers_.end());
1450  players_not_advanced_.erase(&*player);
1451 
1452  const bool game_ended = players_.empty() || (host && !started_);
1453 
1454  auto user = player;
1455 
1456  LOG_GAME
1457  << user->client_ip()
1458  << "\t" << user->info().name()
1459  << ((game_ended && !(observer && destruct)) ? (started_ ? "\tended" : "\taborted") : "\thas left")
1460  << " game:\t\"" << name_ << "\" (" << id_ << ", " << db_id_ << ")"
1461  << (game_ended && started_ && !(observer && destruct)
1462  ? " at turn: " + lexical_cast_default<std::string, std::size_t>(current_turn())
1463  + " with reason: '" + termination_reason() + "'"
1464  : "")
1465  << (observer ? " as an observer" : "") << (disconnect ? " and disconnected" : "") << ".";
1466 
1467  if(game_ended && started_ && !(observer && destruct)) {
1468  send_server_message_to_all(user->info().name() + " ended the game.", player);
1469  }
1470 
1471  if(game_ended || destruct) {
1472  owner_ = player_connections_.end();
1473  return game_ended;
1474  }
1475 
1476  // Don't mark_available() since the player got already removed from the
1477  // games_and_users_list_.
1478  if(!disconnect) {
1479  user->info().mark_available();
1480  }
1481 
1482  if(observer) {
1483  send_observerquit(user);
1484  } else {
1485  send_and_record_server_message(user->info().name()
1486  + (disconnect ? " has disconnected." : " has left the game."), player);
1487  }
1488 
1489  // If the player was host choose a new one.
1490  if(host) {
1491  owner_ = players_.front();
1492  notify_new_host();
1493  }
1494 
1495  bool ai_transfer = false;
1496 
1497  // Look for all sides the player controlled and drop them.
1498  // (Give them to the host.
1499  for(unsigned side_index = 0; side_index < sides_.size(); ++side_index) {
1500  auto side = sides_[side_index];
1501 
1502  if(side != player) {
1503  continue;
1504  }
1505 
1506  if(side_controllers_[side_index] == side_controller::type::ai) {
1507  ai_transfer = true;
1508  }
1509 
1510  change_controller(side_index, owner_, username(owner_));
1511 
1512  // Check whether the host is actually a player and make him one if not.
1513  if(!is_player(owner_)) {
1514  DBG_GAME << "making the owner a player...";
1515  owner_->info().set_status(player::PLAYING);
1516  observers_.erase(std::remove(observers_.begin(), observers_.end(), owner_), observers_.end());
1517  players_.push_back(owner_);
1519  }
1520 
1521  // send the host a notification of removal of this side
1522  const std::string side_drop = lexical_cast_default<std::string, std::size_t>(side_index + 1);
1523 
1524  simple_wml::document drop;
1525  auto& node_side_drop = drop.root().add_child("side_drop");
1526 
1527  node_side_drop.set_attr_dup("side_num", side_drop.c_str());
1528  node_side_drop.set_attr_dup("controller", side_controller::get_string(side_controllers_[side_index]).c_str());
1529 
1530  DBG_GAME << "*** sending side drop: \n" << drop.output();
1531 
1532  server.send_to_player(owner_, drop);
1533  }
1534 
1535  if(ai_transfer) {
1536  send_and_record_server_message("AI sides transferred to host.");
1537  }
1538 
1540 
1541  send_user_list(player);
1542  return false;
1543 }
1544 
1545 void game::send_user_list(std::optional<player_iterator> exclude)
1546 {
1547  // If the game hasn't started yet, then send all players a list of the users in the game.
1548  if(started_ /*|| description_ == nullptr*/) {
1549  return;
1550  }
1551 
1553  simple_wml::node& list = cfg.root();
1554 
1555  for(auto pl : all_game_users()) {
1556  simple_wml::node& user = list.add_child("user");
1557 
1558  // Don't need to duplicate pl->info().name().c_str() because the
1559  // document will be destroyed by the end of the function
1560  user.set_attr_dup("name", pl->info().name().c_str());
1561  user.set_attr("host", is_owner(pl) ? "yes" : "no");
1562  user.set_attr("observer", is_observer(pl) ? "yes" : "no");
1563  }
1564 
1565  send_data(cfg, exclude);
1566 }
1567 
1569 {
1570  assert(sender == owner_);
1571  players_not_advanced_.clear();
1572  for(auto user_ptr : all_game_users()) {
1573  if(user_ptr != sender) {
1574  players_not_advanced_.insert(&*user_ptr);
1575  }
1576  }
1577  started_ = false;
1578 }
1579 
1581 {
1582  send_server_message_to_all(user->info().name() + " advances to the next scenario", user);
1583 
1584  simple_wml::document cfg_scenario;
1585  simple_wml::node& next_scen = cfg_scenario.root().add_child("next_scenario");
1586  level_.root().copy_into(next_scen);
1587  next_scen.set_attr("started", started_ ? "yes" : "no");
1588 
1589  DBG_GAME << "****\n loading next scenario for a client. sides info = ";
1591  DBG_GAME << "****";
1592 
1593  //
1594  // Change the controller to match that client.
1595  //
1596  // FIXME: This breaks scenario transitions with mp connect screen shown.
1597  //
1598  // FIXME: This causes bugs, esp if controller have changed since the
1599  // beginning of the next scenario
1600  //
1601  // There are currently 2 possible ideas to fix this issue:
1602  //
1603  // 1) When the scenario starts, we store the controllers at that
1604  // point and use that data when a client loads the the next
1605  // scenario (here)
1606  //
1607  // 2) When a client loads the next scenario we send it the
1608  // observers' starting point (meaning we don't change sides
1609  // here), and then we send that side an automatic controller
1610  // change later.
1611  //
1612  simple_wml::document doc_controllers;
1613  simple_wml::node& cfg_controllers = doc_controllers.root().add_child("controllers");
1614 
1615  for(const auto& side_user : sides_) {
1616  simple_wml::node& cfg_controller = cfg_controllers.add_child("controller");
1617  cfg_controller.set_attr("is_local", side_user == user ? "yes" : "no");
1618  }
1619 
1620  server.send_to_player(user, cfg_scenario);
1621  server.send_to_player(user, doc_controllers);
1622 
1623  players_not_advanced_.erase(&*user);
1624 
1625  // Send the player the history of the game to-date.
1626  send_history(user);
1627 
1628  // Send observer join of all the observers in the game to the user.
1629  send_observerjoins(user);
1630 }
1631 
1632 template<typename Container>
1633 void game::send_to_players(simple_wml::document& data, const Container& players, std::optional<player_iterator> exclude)
1634 {
1635  for(const auto& player : players) {
1636  if(player != exclude) {
1637  server.send_to_player(player, data);
1638  }
1639  }
1640 }
1641 
1642 void game::send_data(simple_wml::document& data, std::optional<player_iterator> exclude)
1643 {
1644  send_to_players(data, all_game_users(), exclude);
1645 }
1646 
1648  const simple_wml::string_span& sides,
1649  std::optional<player_iterator> exclude)
1650 {
1651  std::vector<int> sides_vec = ::split<int>(sides, ::split_conv_impl);
1652 
1653  DBG_GAME << __func__ << "...";
1654 
1655  decltype(players_) filtered_players;
1656 
1657  std::copy_if(players_.begin(), players_.end(), std::back_inserter(filtered_players),
1658  [this, &sides_vec](player_iterator user) { return controls_side(sides_vec, user); });
1659 
1660  send_to_players(data, filtered_players, exclude);
1661 }
1662 
1663 bool game::controls_side(const std::vector<int>& sides, player_iterator player) const
1664 {
1665  for(int side : sides) {
1666  std::size_t side_index = side - 1;
1667 
1668  if(side_index < sides_.size() && sides_[side_index] == player) {
1669  return true;
1670  }
1671  }
1672 
1673  return false;
1674 }
1675 
1676 std::string game::has_same_ip(player_iterator user) const
1677 {
1678  const user_vector users = all_game_users();
1679  const std::string ip = user->client_ip();
1680 
1681  std::string clones;
1682  for(auto u : users) {
1683  if(ip == u->client_ip() && user != u) {
1684  clones += (clones.empty() ? "" : ", ") + u->info().name();
1685  }
1686  }
1687 
1688  return clones;
1689 }
1690 
1691 void game::send_observerjoins(std::optional<player_iterator> player)
1692 {
1693  for(auto ob : observers_) {
1694  if(ob == player) {
1695  continue;
1696  }
1697 
1699  cfg.root().add_child("observer").set_attr_dup("name", ob->info().name().c_str());
1700 
1701  if(!player) {
1702  // Send to everyone except the observer in question.
1703  send_data(cfg, ob);
1704  } else {
1705  // Send to the (new) user.
1706  server.send_to_player(*player, cfg);
1707  }
1708  }
1709 }
1710 
1712 {
1713  simple_wml::document observer_quit;
1714 
1715  // Don't need to dup the attribute because this document is short-lived.
1716  observer_quit.root()
1717  .add_child("observer_quit")
1718  .set_attr_dup("name", observer->info().name().c_str());
1719 
1720  send_data(observer_quit, observer);
1721 }
1722 
1724 {
1725  if(history_.empty()) {
1726  return;
1727  }
1728 
1729  // we make a new document based on converting to plain text and
1730  // concatenating the buffers.
1731  // TODO: Work out how to concentate buffers without decompressing.
1732  std::string buf;
1733  for(auto& h : history_) {
1734  buf += h->output();
1735  }
1736 
1737  try {
1738  auto doc = std::make_unique<simple_wml::document>(buf.c_str(), simple_wml::INIT_STATIC);
1739  doc->compress();
1740 
1741  server.send_to_player(player, *doc);
1742 
1743  history_.clear();
1744  history_.push_back(std::move(doc));
1745  } catch(const simple_wml::error& e) {
1746  WRN_CONFIG << __func__ << ": simple_wml error: " << e.message;
1747  }
1748 }
1749 
1750 static bool is_invalid_filename_char(char c)
1751 {
1752  return !(isalnum(c) ||
1753  (c == '_') ||
1754  (c == '-') ||
1755  (c == '.') ||
1756  (c == '(') ||
1757  (c == ')') ||
1758  (c == '#') ||
1759  (c == ',') ||
1760  (c == '!') ||
1761  (c == '^') ||
1762  (c == '+') ||
1763  (c == '=') ||
1764  (c == '@') ||
1765  (c == '%') ||
1766  (c == '\'')
1767  );
1768 }
1769 
1771 {
1772  std::stringstream name;
1773  name << (*starting_pos(level_.root()))["name"] << " Turn " << current_turn() << " (" << db_id_ << ").bz2";
1774  std::string filename(name.str());
1775  std::replace(filename.begin(), filename.end(), ' ', '_');
1776  filename.erase(std::remove_if(filename.begin(), filename.end(), is_invalid_filename_char), filename.end());
1777  return filename;
1778 }
1779 
1781 {
1782  if(!save_replays_ || !started_ || history_.empty()) {
1783  return;
1784  }
1785 
1786  std::string replay_commands;
1787  for(const auto& h : history_) {
1788  const simple_wml::node::child_list& turn_list = h->root().children("turn");
1789 
1790  for(const simple_wml::node* turn : turn_list) {
1791  replay_commands += simple_wml::node_to_string(*turn);
1792  }
1793  }
1794 
1795  history_.clear();
1796 
1797  std::stringstream replay_data;
1798  try {
1799  // level_.set_attr_dup("label", name.str().c_str());
1800 
1801  // Used by replays.wesnoth.org as of December 2017. No client usecases.
1802  level_.set_attr_dup("mp_game_title", name_.c_str());
1803 
1804  const bool has_old_replay = level_.child("replay") != nullptr;
1805 
1806  // If there is already a replay in the level_, which means this is a reloaded game,
1807  // then we don't need to add the [start] in the replay.
1808  replay_data
1809  << level_.output()
1810  // This can result in having 2 [replay] at toplevel since level_ can contain one already. But the
1811  // client can handle this (simply merges them).
1812  << "[replay]\n"
1813  // The [start] is generated at the clients and not sent over the network so we add it here.
1814  // It usually contains some checkup data that is used to check whether the calculated results
1815  // match the ones calculated in the replay. But that's not necessary
1816  << (has_old_replay ? "" : "\t[command]\n\t\t[start]\n\t\t[/start]\n\t[/command]\n")
1817  << replay_commands << "[/replay]\n";
1818 
1819  std::string replay_data_str = replay_data.str();
1820  simple_wml::document replay(replay_data_str.c_str(), simple_wml::INIT_STATIC);
1821 
1822  std::string filename = get_replay_filename();
1823  DBG_GAME << "saving replay: " << filename;
1824 
1826  (*os) << replay.output_compressed(true);
1827 
1828  if(!os->good()) {
1829  ERR_GAME << "Could not save replay! (" << filename << ")";
1830  }
1831  } catch(const simple_wml::error& e) {
1832  WRN_CONFIG << __func__ << ": simple_wml error: " << e.message;
1833  }
1834 }
1835 
1836 void game::record_data(std::unique_ptr<simple_wml::document> data)
1837 {
1838  data->compress();
1839  history_.push_back(std::move(data));
1840 }
1841 
1843 {
1844  history_.clear();
1845 }
1846 
1848 {
1849  description_ = desc;
1850  if(!password_.empty()) {
1851  description_->set_attr("password", "yes");
1852  }
1853 }
1854 
1855 void game::set_termination_reason(const std::string& reason)
1856 {
1857  /* if (reason == "out of sync") {
1858  simple_wml::string_span era;
1859  if (level_.child("era")) {
1860  era = level_.child("era")->attr("id");
1861  }
1862  termination_ = "out of sync - " + era.to_string();
1863  }*/
1864  if(termination_.empty()) {
1865  termination_ = reason;
1866  }
1867 }
1868 
1870 {
1871  user_vector res;
1872 
1873  res.insert(res.end(), players_.begin(), players_.end());
1874  res.insert(res.end(), observers_.begin(), observers_.end());
1875 
1876  return res;
1877 }
1878 
1879 std::string game::debug_player_info() const
1880 {
1881  std::stringstream result;
1882  result << "game id: " << id_ << ", " << db_id_ << "\n";
1883 
1884  for(auto user : players_) {
1885  result << "player: " << user->info().name().c_str() << "\n";
1886  }
1887 
1888  for(auto user : observers_) {
1889  result << "observer: " << user->info().name().c_str() << "\n";
1890  }
1891 
1892  return result.str();
1893 }
1894 
1895 std::string game::debug_sides_info() const
1896 {
1897  std::stringstream result;
1898  result << "game id: " << id_ << ", " << db_id_ << "\n";
1900 
1901  result << "\t\t level, server\n";
1902 
1903  for(const simple_wml::node* s : sides) {
1904  result
1905  << "side " << (*s)["side"].to_int()
1906  << " :\t" << (*s)["controller"].to_string()
1907  << "\t, " << side_controller::get_string(side_controllers_[(*s)["side"].to_int() - 1])
1908  << "\t( " << (*s)["current_player"].to_string() << " )\n";
1909  }
1910 
1911  return result.str();
1912 }
1913 
1914 std::optional<player_iterator> game::find_user(const simple_wml::string_span& name)
1915 {
1916  auto player { player_connections_.get<name_t>().find(name.to_string()) };
1917  if(player != player_connections_.get<name_t>().end()) {
1918  return player_connections_.project<0>(player);
1919  } else {
1920  return {};
1921  }
1922 }
1923 
1924 void game::send_and_record_server_message(const char* message, std::optional<player_iterator> exclude)
1925 {
1926  auto doc = std::make_unique<simple_wml::document>();
1927  send_server_message(message, {}, doc.get());
1928  send_data(*doc, exclude);
1929 
1930  if(started_) {
1931  record_data(std::move(doc));
1932  }
1933 }
1934 
1935 void game::send_server_message_to_all(const char* message, std::optional<player_iterator> exclude)
1936 {
1938  send_server_message(message, {}, &doc);
1939  send_data(doc, exclude);
1940 }
1941 
1942 void game::send_server_message(const char* message, std::optional<player_iterator> player, simple_wml::document* docptr) const
1943 {
1944  simple_wml::document docbuf;
1945  if(docptr == nullptr) {
1946  docptr = &docbuf;
1947  }
1948 
1949  simple_wml::document& doc = *docptr;
1950 
1951  if(started_) {
1952  simple_wml::node& cmd = doc.root().add_child("turn");
1953  simple_wml::node& cfg = cmd.add_child("command");
1954  cfg.set_attr("undo", "no");
1955  simple_wml::node& msg = cfg.add_child("speak");
1956 
1957  msg.set_attr("id", "server");
1958  msg.set_attr_dup("message", message);
1959  std::stringstream ss;
1960  ss << ::std::time(nullptr);
1961  msg.set_attr_dup("time", ss.str().c_str());
1962  } else {
1963  simple_wml::node& msg = doc.root().add_child("message");
1964 
1965  msg.set_attr("sender", "server");
1966  msg.set_attr_dup("message", message);
1967  }
1968 
1969  if(player) {
1970  server.send_to_player(*player, doc);
1971  }
1972 }
1973 
1974 bool game::is_reload() const
1975 {
1976  const simple_wml::node& multiplayer = get_multiplayer(level_.root());
1977  return multiplayer.has_attr("savegame") && multiplayer["savegame"].to_bool();
1978 }
1979 
1980 } // namespace wesnothd
node & add_child(const char *name)
Definition: simple_wml.cpp:466
const char * const_iterator
Definition: simple_wml.hpp:47
bool save_replays_
Whether to save a replay of this game.
Definition: game.hpp:890
void remove()
Removes a tip.
Definition: tooltip.cpp:111
const string_span & attr(const char *key) const
Definition: simple_wml.hpp:129
bool is_muted_observer(player_iterator player) const
Definition: game.cpp:158
void handle_choice(const simple_wml::node &data, player_iterator user)
Handle a choice requested by a client, such as changing a side&#39;s controller, if initiated by WML/lua...
Definition: game.cpp:1205
#define DBG_GAME
Definition: game.cpp:35
std::set< const player_record * > players_not_advanced_
in multiplayer campaigns it can happen that some players are still in the previous scenario keep trac...
Definition: game.hpp:884
int db_id_
Used for unique identification of games played in the database.
Definition: game.hpp:810
void send_and_record_server_message(const char *message, std::optional< player_iterator > exclude={})
Send data to all players in this game except &#39;exclude&#39;.
Definition: game.cpp:1924
std::string to_string() const
Definition: simple_wml.cpp:183
void send_observerquit(player_iterator observer)
Definition: game.cpp:1711
const std::string & name() const
Definition: game.hpp:81
void save_replay()
Move the level information and recorded history into a replay file and save it.
Definition: game.cpp:1780
player_connections & player_connections_
Definition: game.hpp:792
void mute_observer(const simple_wml::node &mute, player_iterator muter)
Mute an observer or give a message of all currently muted observers if no name is given...
Definition: game.cpp:695
std::optional< player_iterator > ban_user(const simple_wml::node &ban, player_iterator banner)
Ban a user by name.
Definition: game.cpp:806
void send_to_players(simple_wml::document &data, const Container &players, std::optional< player_iterator > exclude={})
Send data to all players except those excluded.
Definition: game.cpp:1633
bool has_attr(const char *key) const
Definition: simple_wml.cpp:405
void process_change_turns_wml(simple_wml::document &data, player_iterator user)
Handles incoming [change_turns_wml] data.
Definition: game.cpp:1276
bool is_player(player_iterator player) const
Definition: game.cpp:171
std::vector< std::unique_ptr< simple_wml::document > > history_
Replay data.
Definition: game.hpp:860
const simple_wml::node::child_list & get_sides_list() const
Definition: game.hpp:180
void process_message(simple_wml::document &data, player_iterator user)
Sends an ingame message to all other players.
Definition: game.cpp:878
bool is_current_player(player_iterator player) const
Definition: game.hpp:616
New lexcical_cast header.
user_vector muted_observers_
A vector of muted observers.
Definition: game.hpp:826
void update_side_data()
Resets the side configuration according to the scenario data.
Definition: game.cpp:400
const char * end() const
Definition: simple_wml.hpp:92
bool player_is_banned(player_iterator player, const std::string &name) const
Definition: game.cpp:665
std::string username(player_iterator pl) const
Definition: game.cpp:176
node & set_attr_int(const char *key, int value)
Definition: simple_wml.cpp:441
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
std::string_view data
Definition: picture.cpp:206
node & set_attr(const char *key, const char *value)
Definition: simple_wml.cpp:413
std::optional< player_iterator > find_user(const simple_wml::string_span &name)
Shortcut to a convenience function for finding a user by name.
Definition: game.cpp:1914
std::size_t current_side() const
Definition: game.hpp:599
int nsides_
Number of sides in the current scenario.
Definition: game.hpp:835
user_vector observers_
A vector of observers (members not owning a side).
Definition: game.hpp:824
void send_leave_game(player_iterator user) const
Tells a player to leave the game.
Definition: game.cpp:768
bool is_observer(player_iterator player) const
Definition: game.cpp:153
bool all_observers_muted_
Whether all observers should be treated as muted.
Definition: game.hpp:872
#define h
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
bool is_member(player_iterator player) const
Definition: game.hpp:99
std::string termination_
The reason the game ended.
Definition: game.hpp:887
randomness::mt_rng rng_
A wrapper for mersenne twister rng which generates randomness for this game.
Definition: game.hpp:895
node & set_attr_dup(const char *key, const char *value)
Definition: simple_wml.hpp:278
const child_list & children(const char *name) const
Definition: simple_wml.cpp:635
bool no_children() const
Definition: simple_wml.hpp:171
player_iterator owner_
The game host or later owner (if the host left).
Definition: game.hpp:818
bool process_turn(simple_wml::document &data, player_iterator user)
Handles [end_turn], repackages [commands] with private [speak]s in them and sends the data...
Definition: game.cpp:955
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
player_connections::const_iterator player_iterator
void handle_add_side_wml()
Adds a new, empty side owned by no one.
Definition: game.cpp:1134
std::vector< std::string > bans_
List of banned IPs.
Definition: game.hpp:875
bool is_legal_command(const simple_wml::node &command, player_iterator user)
Definition: game.cpp:890
std::unique_ptr< document > clone()
node * child(const char *name)
Definition: simple_wml.hpp:262
const char * output()
node * child(const char *name)
Definition: simple_wml.cpp:608
const char * begin() const
Definition: simple_wml.hpp:91
bmi::multi_index_container< player_record, bmi::indexed_by< bmi::ordered_unique< bmi::tag< socket_t >, bmi::const_mem_fun< player_record, const any_socket_ptr, &player_record::socket > >, bmi::hashed_unique< bmi::tag< name_t >, bmi::const_mem_fun< player_record, const std::string &, &player_record::name > >, bmi::ordered_non_unique< bmi::tag< game_t >, bmi::const_mem_fun< player_record, int, &player_record::game_id > > > > player_connections
int last_choice_request_id_
The ID of the last request received from a client.
Definition: game.hpp:900
void truncate_message(const simple_wml::string_span &str, simple_wml::node &message)
Function to ensure a text message is within the allowed length.
bool end_turn(int new_side)
Function which should be called every time a player ends their turn (i.e.
Definition: game.cpp:1305
static lg::log_domain log_config("config")
void reset_sides()
calculates the initial value for sides_, side_controllerds_, nsides_
Definition: game.cpp:389
void process_whiteboard(simple_wml::document &data, player_iterator user)
Handles incoming [whiteboard] data.
Definition: game.cpp:1249
int num_turns_
The maximum number of turns before the game ends.
Definition: game.hpp:870
void send_history(player_iterator sock) const
Definition: game.cpp:1723
void update_turn_data()
Set or update the current and max turn values in the game&#39;s description.
Definition: game.cpp:1337
bool allow_observers() const
Definition: game.cpp:148
void handle_controller_choice(const simple_wml::node &data)
Handle a request to change a side&#39;s controller.
Definition: game.cpp:1141
bool describe_slots()
Set the description to the number of available slots.
Definition: game.cpp:634
void transfer_side_control(player_iterator player, const simple_wml::node &cfg)
Lets a player owning a side give it to another player or observer.
Definition: game.cpp:472
static bool is_invalid_filename_char(char c)
Definition: game.cpp:1750
void handle_random_choice()
Send a randomly generated number to the requestor.
Definition: game.cpp:1113
static lg::log_domain log_server("server")
static int db_id_num
Incremented to retrieve a unique ID per wesnothd instance for game instances within the database...
Definition: game.hpp:804
#define WRN_CONFIG
Definition: game.cpp:38
std::string get_replay_filename()
Definition: game.cpp:1770
std::optional< player_iterator > current_player() const
Definition: game.hpp:607
void new_scenario(player_iterator sender)
When the host sends the new scenario of a mp campaign.
Definition: game.cpp:1568
void start_game(player_iterator starter)
Starts the game (if a new game) or starts the next scenario of an MP campaign.
Definition: game.cpp:251
const user_vector all_game_users() const
Definition: game.cpp:1869
bool controls_side(const std::vector< int > &sides, player_iterator player) const
Function which returns true if &#39;player&#39; controls any of the sides specified in &#39;sides&#39;.
Definition: game.cpp:1663
void perform_controller_tweaks()
This is performed just before starting and before the [start_game] signal.
Definition: game.cpp:196
bool remove_player(player_iterator player, const bool disconnect=false, const bool destruct=false)
Removes a user from the game.
Definition: game.cpp:1435
int id_
This game&#39;s ID within wesnothd.
Definition: game.hpp:799
std::unique_ptr< simple_wml::document > change_controller_type(const std::size_t side_index, player_iterator player, const std::string &player_name)
Tell everyone else but the source player that the controller type changed.
Definition: game.cpp:607
void send_data(simple_wml::document &data, std::optional< player_iterator > exclude={})
Send data to all players and observers except those excluded.
Definition: game.cpp:1642
std::optional< player_iterator > kick_member(const simple_wml::node &kick, player_iterator kicker)
Kick a user from this game by name.
Definition: game.cpp:774
void load_next_scenario(player_iterator user)
A user asks for the next scenario to advance to.
Definition: game.cpp:1580
bool is_owner(player_iterator player) const
Definition: game.hpp:90
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:40
void send_to_player(player_iterator player, simple_wml::document &data)
Definition: server.hpp:79
static simple_wml::node * starting_pos(simple_wml::node &data)
The non-const version.
Definition: game.hpp:149
void send_server_message(const char *message, std::optional< player_iterator > player={}, simple_wml::document *doc=nullptr) const
Send a server message to the specified player.
Definition: game.cpp:1942
std::size_t i
Definition: function.cpp:968
std::vector< std::string > name_bans_
List of banned usernames.
Definition: game.hpp:877
void record_data(std::unique_ptr< simple_wml::document > data)
Records a WML document in the game&#39;s history.
Definition: game.cpp:1836
node & set_attr_dup(const char *key, const char *value)
Definition: simple_wml.cpp:429
static constexpr std::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
void send_observerjoins(std::optional< player_iterator > player={})
Send a document per observer in the game.
Definition: game.cpp:1691
static map_location::DIRECTION s
std::vector< side_controller::type > side_controllers_
A vector containiner the controller type for each side.
Definition: game.hpp:832
void change_controller(const std::size_t side_index, player_iterator player, const std::string &player_name, const bool player_left=true)
Send [change_controller] message to tell all clients the new controller&#39;s name or controller type (hu...
Definition: game.cpp:575
void remove_child(const char *name, std::size_t index)
Definition: simple_wml.cpp:603
std::vector< node * > child_list
Definition: simple_wml.hpp:126
bool add_player(player_iterator player, bool observer=false)
Add a user to the game.
Definition: game.cpp:1352
std::size_t current_turn() const
Definition: game.hpp:212
Declarations for File-IO.
int current_side_index_
The index of the current side.
Definition: game.hpp:868
side_vector sides_
A vector of side owners.
Definition: game.hpp:829
void notify_new_host()
In case of a host transfer, notify the new host about its status.
Definition: game.cpp:623
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:72
void set_description(simple_wml::node *desc)
Set the game&#39;s description.
Definition: game.cpp:1847
std::string has_same_ip(player_iterator user) const
Checks whether a user has the same IP as any other members of this game.
Definition: game.cpp:1676
void send_data_sides(simple_wml::document &data, const simple_wml::string_span &sides, std::optional< player_iterator > exclude={})
Sends a document to the provided list of sides.
Definition: game.cpp:1647
static const simple_wml::node & get_multiplayer(const simple_wml::node &root)
returns const so that operator [] won&#39;t create empty keys if not existent
Definition: game.cpp:138
std::string observer
std::string list_users(user_vector users) const
Definition: game.cpp:181
simple_wml::node * description_
Pointer to the game&#39;s description in the games_and_users_list_.
Definition: game.hpp:863
std::string name_
The name of the game.
Definition: game.hpp:813
void unban_user(const simple_wml::node &unban, player_iterator unbanner)
Unban a user by name.
Definition: game.cpp:848
std::string debug_sides_info() const
Helps debugging controller tweaks.
Definition: game.cpp:1895
static int id_num
Incremented to retrieve a unique ID for game instances within wesnothd.
Definition: game.hpp:797
bool one_child() const
Definition: simple_wml.hpp:172
std::vector< std::string > split(const config_attribute_value &val)
std::vector< player_iterator > user_vector
Definition: game.hpp:32
bool is_reload() const
Definition: game.cpp:1974
void send_user_list(std::optional< player_iterator > exclude={})
Function to send a list of users to all clients.
Definition: game.cpp:1545
const std::string & termination_reason() const
Provides the reason the game was ended.
Definition: game.hpp:523
std::string replay_save_path_
Where to save the replay of this game.
Definition: game.hpp:892
int current_turn_
The game&#39;s current turn.
Definition: game.hpp:866
Standard logging facilities (interface).
user_vector players_
A vector of players (members owning a side).
Definition: game.hpp:821
std::string message
Definition: exceptions.hpp:30
void send_server_message_to_all(const char *message, std::optional< player_iterator > exclude={})
Sends a message to all players in this game that aren&#39;t excluded.
Definition: game.cpp:1935
std::string password_
The password needed to join the game.
Definition: game.hpp:815
#define e
void send_muted_observers(player_iterator user) const
Sends a message either stating that all observers are muted or listing the observers that are muted...
Definition: game.cpp:683
int side_number
Definition: game_info.hpp:40
#define LOG_GAME
Definition: game.cpp:34
bool started_
Whether the game has been started or not.
Definition: game.hpp:837
bool send_taken_side(simple_wml::document &cfg, const simple_wml::node *side) const
Tell the host who owns a side.
Definition: game.cpp:327
void copy_into(node &n) const
Definition: simple_wml.cpp:807
mock_char c
std::string debug_player_info() const
Helps debugging player and observer lists.
Definition: game.cpp:1879
void unmute_observer(const simple_wml::node &unmute, player_iterator unmuter)
Unmute an observer or unmute all currently muted observers if no name is given.
Definition: game.cpp:736
simple_wml::document level_
The current scenario data.
Definition: game.hpp:857
void clear_history()
Clears the history of recorded WML documents.
Definition: game.cpp:1842
void mute_all_observers()
Toggles whether all observers are muted or not.
Definition: game.cpp:673
bool take_side(player_iterator user)
Figures out which side to take and tells that side to the game owner.
Definition: game.cpp:348
void set_termination_reason(const std::string &reason)
Sets the termination reason for this game.
Definition: game.cpp:1855
game(wesnothd::server &server, player_connections &player_connections, player_iterator host, const std::string &name="", bool save_replays=false, const std::string &replay_save_path="")
Definition: game.cpp:78
std::string node_to_string(const node &n)
Definition: simple_wml.cpp:795
const std::string observer_team_name
observer team name used for observer team chat
Definition: game_config.cpp:90
#define ERR_GAME
Definition: game.cpp:32
bool save_replays()
Definition: game.cpp:756
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46