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