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