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