The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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
17
18#include "filesystem.hpp"
19#include "lexical_cast.hpp"
20#include "log.hpp"
24#include "utils/math.hpp"
25
26#include <iomanip>
27#include <sstream>
28
29#include <boost/coroutine/exceptions.hpp>
30
31static 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
37static lg::log_domain log_config("config");
38#define WRN_CONFIG LOG_STREAM(warn, log_config)
39
40namespace
41{
42void 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
49template<typename TResult, typename TConvert>
50std::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
72namespace wesnothd
73{
74
75int game::id_num = 1;
76int game::db_id_num = 1;
77
79 player_iterator host,
80 queue_type::type queue_type,
81 int queue_id,
82 const std::string& name,
83 bool save_replays,
84 const std::string& replay_save_path)
85 : server(server)
86 , player_connections_(player_connections)
87 , id_(id_num++)
88 , db_id_(db_id_num++)
89 , name_(name)
90 , password_()
91 , owner_(host)
92 , players_()
93 , observers_()
94 , muted_observers_()
95 , sides_()
96 , side_controllers_()
97 , nsides_(0)
98 , started_(false)
99 , level_()
100 , history_()
101 , chat_history_()
102 , description_(nullptr)
103 , description_updated_(false)
104 , current_turn_(0)
105 , current_side_index_(0)
106 , next_side_index_(0)
107 , num_turns_(0)
108 , all_observers_muted_(false)
109 , bans_()
110 , name_bans_()
111 , players_not_advanced_()
112 , termination_()
113 , save_replays_(save_replays)
114 , replay_save_path_(replay_save_path)
115 , rng_()
116 , last_choice_request_id_(-1) /* or maybe 0 ? it shouldn't matter*/
117 , queue_type_(queue_type)
118 , queue_id_(queue_id)
119{
120 players_.push_back(owner_);
121
122 // Mark the host as unavailable in the lobby.
123 owner_->info().mark_available(id_, name_);
124 owner_->info().set_status(player::PLAYING);
125}
126
128{
129 try {
130 save_replay();
131
132 for(player_iterator user_ptr : all_game_users()) {
133 remove_player(user_ptr, false, true);
134 }
135
137 } catch(const boost::coroutines::detail::forced_unwind&) {
138 ERR_GAME << "Caught forced_unwind in game destructor!";
139 } catch(...) {
140 ERR_GAME << "Caught other exception in game destructor: " << utils::get_unknown_exception_type();
141 }
142}
143
144/** returns const so that operator [] won't create empty keys if not existent */
146{
147 if(const simple_wml::node* multiplayer = root.child("multiplayer")) {
148 return *multiplayer;
149 } else {
150 ERR_GAME << "no [multiplayer] found. Returning root";
151 return root;
152 }
153}
154
155const std::string game::get_scenario_id() const
156{
157 return get_multiplayer(level_.root())["mp_scenario"].to_string();
158}
159
161{
162 return get_multiplayer(level_.root())["observer"].to_bool(true);
163}
164
169
171{
172 if(!is_observer(player)) {
173 return false;
174 }
175
177 return true;
178 }
179
181}
182
187
188std::string game::username(player_iterator iter) const
189{
190 return iter->info().name();
191}
192
193std::string game::list_users(const user_vector& users) const
194{
195 std::string list;
196
197 for(auto user : users) {
198 if(!list.empty()) {
199 list += ", ";
200 }
201
202 list += user->info().name();
203 }
204
205 return list;
206}
207
209{
211
212 DBG_GAME << "****\n Performing controller tweaks. sides = ";
214 DBG_GAME << "****";
215
216 update_side_data(); // Necessary to read the level_ and get sides_, etc. updated to match
217
218 for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
219 simple_wml::node& side = *sides[side_index];
220
221 if(side["controller"] != side_controller::none) {
222 if(!sides_[side_index]) {
223 sides_[side_index] = owner_;
224 std::stringstream msg;
225 msg << "Side " << side_index + 1
226 << " had no controller during controller tweaks! The host was assigned control.";
227
228 LOG_GAME << msg.str() << " (game id: " << id_ << ", " << db_id_ << ")";
230 }
231
232 std::string user_name = username(*sides_[side_index]);
233
234 // Issue change_controller command, transferring this side to its owner with proper name and controller.
235 // Ensures that what the server now thinks is true is effected on all of the clients.
236 //
237 // In the server controller tweaks, we want to avoid sending controller change messages to the host.
238 // Doing this has the negative consequence that all of the AI side names are given the owners name.
239 // Therefore, if the side belongs to the host, we pass player_left = true, otherwise player_left = false.
240 change_controller(side_index, *sides_[side_index], user_name, sides_[side_index] == owner_);
241
242 // next line change controller types found in level_ to be what is appropriate for an observer at game
243 // start.
244 side.set_attr("is_local", "no");
245
246 if(!sides_[side_index]) {
247 std::stringstream msg;
248 msg << "Side " << side_index + 1 << " had no controller AFTER controller tweaks! Ruh Roh!";
249 LOG_GAME << msg.str() << " (game id: " << id_ << ", " << db_id_ << ")";
250 }
251 }
252 }
253
254 // This is the last time that update_side_data will actually run, as now the game will start and
255 // started_ will be true.
257
258 // TODO: Does it matter that the server is telling the host to change a bunch of sides?
259 // According to playturn.cpp, the host should ignore all such messages. Still might be better
260 // not to send them at all, although not if it complicates the server code.
261}
262
264{
266 DBG_GAME << "****\n Starting game. sides = ";
268 DBG_GAME << "****";
269
270 // If the game was already started we're actually advancing.
271 const bool advance = started_;
272 started_ = true;
273 // Prevent inserting empty keys when reading.
274 const simple_wml::node& multiplayer = get_multiplayer(level_.root());
275
276 const bool save = multiplayer["savegame"].to_bool();
278 << starter->client_ip() << "\t" << starter->name() << "\t"
279 << (advance ? "advanced" : "started") << (save ? " reloaded" : "") << " game:\t\"" << name_ << "\" (" << id_
280 << ", " << db_id_ << ") with: " << list_users(players_)
281 << ". Settings: map: " << multiplayer["mp_scenario"]
282 << "\tera: " << multiplayer["mp_era"]
283 << "\tXP: " << multiplayer["experience_modifier"]
284 << "\tGPV: " << multiplayer["mp_village_gold"]
285 << "\tfog: " << multiplayer["mp_fog"]
286 << "\tshroud: " << multiplayer["mp_shroud"]
287 << "\tobservers: " << multiplayer["observer"]
288 << "\tshuffle: " << multiplayer["shuffle_sides"]
289 << "\ttimer: " << multiplayer["mp_countdown"]
290 << (multiplayer["mp_countdown"].to_bool()
291 ? "\treservoir time: " + multiplayer["mp_countdown_reservoir_time"].to_string()
292 + "\tinit time: " + multiplayer["mp_countdown_init_time"].to_string()
293 + "\taction bonus: " + multiplayer["mp_countdown_action_bonus"].to_string()
294 + "\tturn bonus: " + multiplayer["mp_countdown_turn_bonus"].to_string()
295 : "");
296
297
298 for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
299 simple_wml::node& side = *sides[side_index];
300
301 if(side["controller"] != side_controller::none) {
302 if(side_index >= sides_.size()) {
303 continue;
304 }
305
306 if(!sides_[side_index]) {
307 std::stringstream msg;
308 msg << "Side " << side_index + 1
309 << " has no controller but should! The host needs to assign control for the game to proceed past "
310 "that side's turn.";
311
312 LOG_GAME << msg.str() << " (game id: " << id_ << ", " << db_id_ << ")";
314 }
315 }
316 }
317
318 DBG_GAME << "Number of sides: " << nsides_;
319 int turn = 1;
320 int side = 0;
321 int next_side = 0;
322
323 // Savegames have a snapshot that tells us which side starts.
324 if(const simple_wml::node* snapshot = level_.root().child("snapshot")) {
325 turn = lexical_cast_default<int>((*snapshot)["turn_at"], 1);
326 side = lexical_cast_default<int>((*snapshot)["playing_team"], 0);
327 if((*snapshot)["init_side_done"].to_bool(false)) {
328 next_side = -1;
329 } else {
330 next_side = side;
331 }
332 LOG_GAME << "Reload from turn: " << turn << ". Current side is: " << side + 1 << ". Next side is: " << next_side + 1;
333 }
334
335 current_turn_ = turn;
336 current_side_index_ = side;
337 next_side_index_ = next_side;
338
339 num_turns_ = lexical_cast_default<int>((*starting_pos(level_.root()))["turns"], -1);
340
344
345 // Send [observer] tags for all observers that are already in the game.
347}
348
350{
351 const std::size_t side_index = (*side)["side"].to_int() - 1;
352
353 // Negative values are casted (int -> std::size_t) to very high values to this check will fail for them too.
354 if(side_index >= sides_.size()) {
355 return false;
356 }
357
358 if(sides_[side_index]) {
359 return false;
360 }
361
362 // We expect that the host will really use our proposed side number. (He could do different...)
363 cfg.root().set_attr_dup("side", (*side)["side"]);
364
365 // Tell the host which side the new player should take.
367 return true;
368}
369
371{
372 DBG_GAME << "take_side...";
373
374 if(started_) {
375 return false;
376 }
377
379 cfg.root().set_attr_dup("name", user->name().c_str());
380
381 // FIXME: The client code (multiplayer.wait.cpp) the host code (connect_engine.cpp) and the server code
382 // (this file) has this code to figure out a fitting side for new players, this is clearly too much.
383 // Check if we can figure out a fitting side.
385
386 for(const simple_wml::node* side : sides) {
387 if(((*side)["controller"] == side_controller::human || (*side)["controller"] == side_controller::reserved)
388 && (*side)["current_player"] == user->name().c_str()) {
389
390 if(send_taken_side(cfg, side)) {
391 return true;
392 }
393 }
394 }
395
396 // If there was no fitting side just take the first available.
397 for(const simple_wml::node* side : sides) {
398 if((*side)["controller"] == side_controller::human) {
399 if(send_taken_side(cfg, side)) {
400 return true;
401 }
402 }
403 }
404
405 DBG_GAME << "take_side: there are no more sides available";
406
407 // If we get here we couldn't find a side to take
408 return false;
409}
410
412{
413 side_controllers_.clear();
414 sides_.clear();
415
416 nsides_ = get_sides_list().size();
417
419 sides_.resize(nsides_);
420}
421
423{
424 // Added by iceiceice: since level_ will now reflect how an observer views the replay start
425 // position and not the current position, the sides_, side_controllers_, players_ info should
426 // not be updated from the level_ after the game has started. Controller changes are now stored
427 // in the history, so an observer that joins will get up to date that way.
428 if(started_) {
429 return;
430 }
431
432 DBG_GAME << "update_side_data...";
434
435 // Remember everyone that is in the game.
436 const user_vector users = all_game_users();
437
438 players_.clear();
439 observers_.clear();
440
441 reset_sides();
442
443 const simple_wml::node::child_list& level_sides = get_sides_list();
444
445 // For each user:
446 // * Find the username.
447 // * Find the side this username corresponds to.
448 for(auto iter : users) {
449
450 bool side_found = false;
451 for(unsigned side_index = 0; side_index < level_sides.size(); ++side_index) {
452 const simple_wml::node* side = level_sides[side_index];
453
454 if(side_index >= sides_.size() || sides_[side_index]) {
455 continue;
456 }
457
458 const simple_wml::string_span& player_id = (*side)["player_id"];
459 const simple_wml::string_span& controller = (*side)["controller"];
460
461 auto type = side_controller::get_enum(controller.to_string());
462 if(player_id == iter->info().name().c_str()) {
463 // We found invalid [side] data. Some message would be cool.
464 if(controller != side_controller::human && controller != side_controller::ai) {
465 continue;
466 }
467
468 if(type) {
469 side_controllers_[side_index] = *type;
470 }
471 sides_[side_index] = iter;
472 side_found = true;
473 } else if(iter == owner_ && controller == side_controller::none) {
474 // the *user == owner_ check has no effect,
475 // it's just an optimisation so that we only do this once.
476 if(type) {
477 side_controllers_[side_index] = *type;
478 }
479 }
480 }
481
482 if(side_found) {
483 players_.push_back(iter);
484 iter->info().set_status(player::PLAYING);
485 } else {
486 observers_.push_back(iter);
487 iter->info().set_status(player::OBSERVING);
488 }
489 }
490
492}
493
495{
496 DBG_GAME << "transfer_side_control...";
497
498 if(!is_player(player) && player != owner_) {
499 send_server_message("You cannot change controllers: not a player.", player);
500 return;
501 }
502
503 // Check the side number.
504 const unsigned int side_num = cfg["side"].to_int();
505 if(side_num < 1 || side_num > sides_.size()) {
506 std::ostringstream msg;
507 msg << "The side number has to be between 1 and " << sides_.size() << ".";
508 send_server_message(msg.str(), player);
509 return;
510 }
511
512 if(side_num > get_sides_list().size()) {
513 send_server_message("Invalid side number.", player);
514 return;
515 }
516
517 const simple_wml::string_span& newplayer_name = cfg["player"];
518 auto old_player = sides_[side_num - 1];
519 const std::string& controller_type = cfg["to"].to_string();
520
521 const std::string old_player_name = old_player ? username(*old_player) : "null";
522
523 // Not supported anymore.
524 if(newplayer_name.empty()) {
525 std::stringstream msg;
526 msg << "Received invalid [change_controller] with no player= attribute specified";
527 DBG_GAME << msg.str();
528 send_server_message(msg.str(), player);
529 return;
530 }
531
532 // Check if the sender actually owns the side he gives away or is the host.
533 if(!(player == old_player || player == owner_)) {
534 std::stringstream msg;
535 msg << "You can't give away side " << side_num << ". It's controlled by '" << old_player_name << "' not you.";
536 DBG_GAME << msg.str();
537 send_server_message(msg.str(), player);
538 return;
539 }
540
541 // find the player that is passed control
542 auto newplayer { find_user(newplayer_name) };
543
544 // Is he in this game?
545 if(!newplayer || !is_member(*newplayer)) {
546 send_server_message(newplayer_name.to_string() + " is not in this game", player);
547 return;
548 }
549
550 if(newplayer == old_player) {
551 // if the player is unchanged and the controller type (human or ai) is also unchanged then nothing to do
552 // else only need to change the controller type rather than the player who controls the side
553 // :droid provides a valid controller_type; :control provides nothing since it's only tranferring control between players regardless of type
554 auto type = side_controller::get_enum(controller_type);
555 if(!type || type == side_controllers_[side_num - 1]) {
556 std::stringstream msg;
557 msg << "Side " << side_num << " is already controlled by " << newplayer_name << ".";
558 send_server_message(msg.str(), player);
559 return;
560 } else {
561 side_controllers_[side_num - 1] = *side_controller::get_enum(controller_type);
562 change_controller_type(side_num - 1, *newplayer, (*newplayer)->info().name());
563 return;
564 }
565 }
566
567 sides_[side_num - 1].reset();
568
569 // If the old player lost his last side, make him an observer.
570 if(old_player && !utils::contains(sides_, old_player) && is_player(*old_player)) {
571 observers_.push_back(*old_player);
572
573 (*old_player)->info().set_status(player::OBSERVING);
574 utils::erase(players_, old_player);
575
576 // Tell others that the player becomes an observer.
577 send_and_record_server_message(old_player_name + " becomes an observer.");
578
579 // Update the client side observer list for everyone except old player.
580 simple_wml::document observer_join;
581 observer_join.root().add_child("observer").set_attr_dup("name", old_player_name.c_str());
582 send_data(observer_join, *old_player);
583 }
584
585 change_controller(side_num - 1, *newplayer, (*newplayer)->info().name(), false);
586
587 // If we gave the new side to an observer add him to players_.
588 if(is_observer(*newplayer)) {
589 players_.push_back(*newplayer);
590 (*newplayer)->info().set_status(player::PLAYING);
591 utils::erase(observers_, newplayer);
592 // Send everyone but the new player the observer_quit message.
593 send_observerquit(*newplayer);
594 }
595}
596
598 const std::size_t side_index, player_iterator player, const std::string& player_name, const bool player_left)
599{
600 DBG_GAME << __func__ << "...";
601
602 const std::string& side = lexical_cast_default<std::string, std::size_t>(side_index + 1);
603 sides_[side_index] = player;
604
605 if(player_left && side_controllers_[side_index] == side_controller::type::ai) {
606 // Automatic AI side transfer.
607 } else {
608 if(started_) {
609 send_and_record_server_message(player_name + " takes control of side " + side + ".");
610 }
611 }
612
613 auto response = change_controller_type(side_index, player, player_name);
614
615 if(started_) {
616 // the purpose of these records is so that observers, replay viewers, etc get controller updates correctly
617 record_data(response->clone());
618 }
619
620 // Tell the new player that he controls this side now.
621 // Just don't send it when the player left the game. (The host gets the
622 // side_drop already.)
623 if(!player_left) {
624 response->root().child("change_controller")->set_attr("is_local", "yes");
625 server.send_to_player(player, *response.get());
626 }
627}
628
629std::unique_ptr<simple_wml::document> game::change_controller_type(const std::size_t side_index, player_iterator player, const std::string& player_name)
630{
631 const std::string& side = std::to_string(side_index + 1);
632 simple_wml::document response;
633 simple_wml::node& change = response.root().add_child("change_controller");
634
635 change.set_attr_dup("side", side.c_str());
636 change.set_attr_dup("player", player_name.c_str());
637
638 change.set_attr_dup("controller", side_controller::get_string(side_controllers_[side_index]).c_str());
639 change.set_attr("is_local", "no");
640
641 send_data(response, player);
642 return response.clone();
643}
644
646{
647 const std::string owner_name = username(owner_);
649 cfg.root().add_child("host_transfer");
650
651 std::string message = owner_name + " has been chosen as the new host.";
654}
655
657{
658 if(started_ || description_ == nullptr) {
659 return;
660 }
661
662 int available_slots = 0;
663 int num_sides = get_sides_list().size();
664 int i = 0;
665
666 for(const simple_wml::node* side : get_sides_list()) {
667 if(((*side)["allow_player"].to_bool(true) == false) || (*side)["controller"] == side_controller::none) {
668 num_sides--;
669 } else if(!sides_[i]) {
670 ++available_slots;
671 }
672
673 ++i;
674 }
675
676 simple_wml::node& slots_cfg = description_for_writing()->child_or_add("slot_data");
677
678 slots_cfg.set_attr_int("vacant", available_slots);
679 slots_cfg.set_attr_int("max", num_sides);
680}
681
682bool game::player_is_banned(player_iterator player, const std::string& name) const
683{
684 return utils::contains(bans_, player->client_ip())
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()) {
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
789utils::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
821utils::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
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"];
899 chat_message::truncate_message(msg, *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 = static_cast<std::size_t>(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(side_index >= sides_.size() || side_index >= side_controllers_.size()) {
1185 "Could not handle [request_choice] [change_controller] with invalid side '" + req["side"].to_string() + "'");
1186 return;
1187 }
1188
1189 if(old_controller != this->side_controllers_[side_index]) {
1191 "Found unexpected old_controller= '" + side_controller::get_string(*old_controller) + "' in [request_choice] [change_controller]");
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
1327void 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
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 {
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
1447bool 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
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) {
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();
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
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
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
1557void 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
1644template<typename Container>
1645void 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
1654void 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
1675bool 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
1688std::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
1703void 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.
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
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
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
1859void 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
1883void 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
1907std::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
1923std::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 const int side_number = (*s)["side"].to_int();
1933 const std::size_t side_index = side_number > 0 ? static_cast<std::size_t>(side_number - 1) : side_controllers_.size();
1934 const std::string side_controller = side_index < side_controllers_.size() ? side_controller::get_string(side_controllers_[side_index]) : "<invalid side>";
1935
1936 result
1937 << "side " << side_number
1938 << " :\t" << (*s)["controller"].to_string()
1939 << "\t, " << side_controller
1940 << "\t( " << (*s)["current_player"].to_string() << " )\n";
1941 }
1942
1943 return result.str();
1944}
1945
1946utils::optional<player_iterator> game::find_user(const simple_wml::string_span& name)
1947{
1948 auto player { player_connections_.get<name_t>().find(name.to_string()) };
1949 if(player != player_connections_.get<name_t>().end()) {
1950 return player_connections_.project<0>(player);
1951 } else {
1952 return {};
1953 }
1954}
1955
1956void game::send_and_record_server_message(const char* message, utils::optional<player_iterator> exclude)
1957{
1958 auto doc = std::make_unique<simple_wml::document>();
1959 send_server_message(message, {}, doc.get());
1960 send_data(*doc, exclude);
1961
1962 if(started_) {
1963 record_data(std::move(doc));
1964 }
1965}
1966
1967void game::send_server_message_to_all(const char* message, utils::optional<player_iterator> exclude)
1968{
1970 send_server_message(message, {}, &doc);
1971 send_data(doc, exclude);
1972}
1973
1974void game::send_server_message(const char* message, utils::optional<player_iterator> player, simple_wml::document* docptr) const
1975{
1976 simple_wml::document docbuf;
1977 if(docptr == nullptr) {
1978 docptr = &docbuf;
1979 }
1980
1981 simple_wml::document& doc = *docptr;
1982
1983 if(started_) {
1984 simple_wml::node& cmd = doc.root().add_child("turn");
1985 simple_wml::node& cfg = cmd.add_child("command");
1986 cfg.set_attr("undo", "no");
1987 simple_wml::node& msg = cfg.add_child("speak");
1988
1989 msg.set_attr("id", "server");
1990 msg.set_attr_dup("message", message);
1991 std::stringstream ss;
1992 ss << chrono::serialize_timestamp(std::chrono::system_clock::now());
1993 msg.set_attr_dup("time", ss.str().c_str());
1994 } else {
1995 simple_wml::node& msg = doc.root().add_child("message");
1996
1997 msg.set_attr("sender", "server");
1998 msg.set_attr_dup("message", message);
1999 }
2000
2001 if(player) {
2003 }
2004}
2005
2007{
2008 const simple_wml::node& multiplayer = get_multiplayer(level_.root());
2009 return multiplayer.has_attr("savegame") && multiplayer["savegame"].to_bool();
2010}
2011
2016
2017int game::get_next_nonempty(int side_index) const
2018{
2019 if(side_index == -1) {
2020 return -1;
2021 }
2022 if(nsides_ == 0) {
2023 return 0;
2024 }
2025 for(int i = 0; i < nsides_; ++i) {
2026 int res = modulo(side_index + i, nsides_, 0);
2027 if(side_controllers_[res] != side_controller::type::none) {
2028 return res;
2029 }
2030 }
2031 return -1;
2032}
2033
2034} // namespace wesnothd
config & add_child(std::string_view key)
Definition config.cpp:436
uint32_t get_next_random()
Get a new random number.
Definition mt_rng.cpp:62
const char * output()
std::unique_ptr< document > clone()
node & set_attr_dup(const char *key, const char *value)
node * child(const char *name)
const string_span & attr(const char *key) const
void remove_child(const char *name, std::size_t index)
bool no_children() const
const child_list & children(const char *name) const
bool has_attr(const char *key) const
node & set_attr_int(const char *key, int value)
node * child(const char *name)
std::vector< node * > child_list
node & add_child(const char *name)
node & child_or_add(const char *name)
node & set_attr(const char *key, const char *value)
void copy_into(node &n) const
node & set_attr_dup(const char *key, const char *value)
bool one_child() const
std::string to_string() const
const char * end() const
const char * begin() const
const char * const_iterator
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:2006
const std::string & termination_reason() const
Provides the reason the game was ended.
Definition game.hpp:556
simple_wml::node * description_for_writing()
Definition game.hpp:496
game(wesnothd::server &server, player_connections &player_connections, player_iterator host, queue_type::type queue_type, int queue_id, const std::string &name="", bool save_replays=false, const std::string &replay_save_path="")
Definition game.cpp:78
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
int current_side_index_
The index of the current side.
Definition game.hpp:947
std::vector< std::unique_ptr< simple_wml::document > > history_
Replay data.
Definition game.hpp:934
bool is_legal_command(const simple_wml::node &command, player_iterator user)
Definition game.cpp:905
const simple_wml::node::child_list & get_sides_list() const
Definition game.hpp:189
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:959
bool is_owner(player_iterator player) const
Definition game.hpp:97
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:678
randomness::mt_rng rng_
A wrapper for mersenne twister rng which generates randomness for this game.
Definition game.hpp:979
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:887
void notify_new_host()
In case of a host transfer, notify the new host about its status.
Definition game.cpp:645
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:422
std::string replay_save_path_
Where to save the replay of this game.
Definition game.hpp:976
void send_leave_game(player_iterator user) const
Tells a player to leave the game.
Definition game.cpp:783
static simple_wml::node * starting_pos(simple_wml::node &data)
The non-const version.
Definition game.hpp:158
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
const std::string & name() const
Definition game.hpp:88
std::vector< std::unique_ptr< simple_wml::document > > chat_history_
Replay chat history data.
Definition game.hpp:936
void perform_controller_tweaks()
This is performed just before starting and before the [start_game] signal.
Definition game.cpp:208
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:906
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:1967
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:945
bool save_replays_
Whether to save a replay of this game.
Definition game.hpp:974
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
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:193
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:656
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:263
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:1956
std::vector< std::string > name_bans_
List of banned usernames.
Definition game.hpp:961
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:873
std::size_t current_side() const
Definition game.hpp:648
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:1946
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:221
std::string username(player_iterator pl) const
Definition game.cpp:188
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:866
int get_next_nonempty(int side_index) const
finds the first side starting at side_index that is non empty.
Definition game.cpp:2017
bool is_member(player_iterator player) const
Definition game.hpp:106
int db_id_
Used for unique identification of games played in the database.
Definition game.hpp:884
void reset_sides()
calculates the initial value for sides_, side_controllerds_, nsides_
Definition game.cpp:411
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:900
bool is_muted_observer(player_iterator player) const
Definition game.cpp:170
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:968
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:494
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:952
bool started_
Whether the game has been started or not.
Definition game.hpp:911
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:971
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
simple_wml::node * description_
Pointer to the game's description in the games_and_users_list_.
Definition game.hpp:939
user_vector observers_
A vector of observers (members not owning a side).
Definition game.hpp:898
static int db_id_num
Incremented to retrieve a unique ID per wesnothd instance for game instances within the database.
Definition game.hpp:878
const std::string get_scenario_id() const
Definition game.cpp:155
void set_description(simple_wml::node *desc)
Set the game's description.
Definition game.cpp:1875
utils::optional< player_iterator > current_player() const
Definition game.hpp:668
int num_turns_
The maximum number of turns before the game ends.
Definition game.hpp:954
bool all_observers_muted_
Whether all observers should be treated as muted.
Definition game.hpp:956
side_vector sides_
A vector of side owners.
Definition game.hpp:903
int nsides_
Number of sides in the current scenario.
Definition game.hpp:909
simple_wml::document level_
The current scenario data.
Definition game.hpp:931
bool take_side(player_iterator user)
Figures out which side to take and tells that side to the game owner.
Definition game.cpp:370
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:597
bool allow_observers() const
Definition game.cpp:160
user_vector players_
A vector of players (members owning a side).
Definition game.hpp:895
utils::optional< player_iterator > get_side_player(std::size_t index) const
Definition game.hpp:660
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:349
int get_next_side_index() const
Definition game.cpp:2012
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:1974
std::string password_
The password needed to join the game.
Definition game.hpp:889
bool is_observer(player_iterator player) const
Definition game.cpp:165
bool is_player(player_iterator player) const
Definition game.cpp:183
int last_choice_request_id_
The ID of the last request received from a client.
Definition game.hpp:984
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:871
bool player_is_banned(player_iterator player, const std::string &name) const
Definition game.cpp:682
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:629
void update_turn_data()
Set or update the current and max turn values in the game's description.
Definition game.cpp:1350
player_iterator owner_
The game host or later owner (if the host left).
Definition game.hpp:892
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:55
void send_to_player(player_iterator player, simple_wml::document &data)
Definition server.hpp:85
const config * cfg
Declarations for File-IO.
#define ERR_GAME
Definition game.cpp:32
#define DBG_GAME
Definition game.cpp:35
#define LOG_GAME
Definition game.cpp:34
static lg::log_domain log_server("server")
static lg::log_domain log_config("config")
#define WRN_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:60
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition chrono.hpp:58
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode=std::ios_base::binary, bool create_directory=true)
std::unique_ptr< std::ostream > scoped_ostream
const std::string observer_team_name
observer team name used for observer team chat
std::string node_to_string(const node &n)
std::size_t erase(Container &container, const Value &value)
Convenience wrapper for using std::remove on a container.
Definition general.hpp:119
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
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:108
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:32
player_connections::const_iterator player_iterator
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 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:145
std::string_view data
Definition picture.cpp:188
std::string filename
Filename.
The base template for associating string values with enum values.
Definition enum_base.hpp:33
static constexpr std::size_t size()
Definition enum_base.hpp:85
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
static map_location::direction s
#define i
#define e
#define h
#define c