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