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