From 6454766e7dce3871e8cb86e09c46417cd5f11e6d Mon Sep 17 00:00:00 2001 From: Gunter Labes Date: Fri, 2 Nov 2007 01:38:56 +0000 Subject: [PATCH] [[MP player tweaks]] * converted sides_ from a multimap of connection and side to a vector of connections * moved trivial functions in the header * simplify some functions and add more error reporting and comments * refine the log message levels and categories * streamline code formatting and try to stay below 80 chars per line * added some special handling for the pseudo games lobby_players_ and not_logged_in_ hopefully no regression... --- src/server/game.cpp | 741 ++++++++++------------- src/server/game.hpp | 116 ++-- src/server/player.cpp | 31 +- src/server/player.hpp | 15 +- src/server/server.cpp | 1296 ++++++++++++++++++++--------------------- 5 files changed, 994 insertions(+), 1205 deletions(-) diff --git a/src/server/game.cpp b/src/server/game.cpp index a757428e724..133f6698904 100644 --- a/src/server/game.cpp +++ b/src/server/game.cpp @@ -22,64 +22,31 @@ #include #include -#define LOG_SERVER LOG_STREAM(info, general) +#define ERR_GAME LOG_STREAM(err, mp_server) +#define LOG_GAME LOG_STREAM(info, mp_server) +#define DBG_GAME LOG_STREAM(debug, mp_server) int game::id_num = 1; -game::game(const player_map& info) : player_info_(&info), id_(id_num++), sides_taken_(9), side_controllers_(9), started_(false), description_(NULL), end_turn_(0), allow_observers_(true), all_observers_muted_(false) +game::game(const player_map& pl) : player_info_(&pl), id_(id_num++), sides_(9), + sides_taken_(9), side_controllers_(9), started_(false), description_(NULL), + end_turn_(0), allow_observers_(true), all_observers_muted_(false) {} -void game::set_owner(network::connection player) -{ - owner_ = player; -} - -bool game::is_owner(network::connection player) const -{ - return (player == owner_); -} - -bool game::is_member(network::connection player) const -{ - return is_player(player) || is_observer(player); -} - -bool game::is_needed(network::connection player) const -{ - return (!players_.empty() && player == owner_); -} - -bool game::is_observer(network::connection player) const -{ +bool game::is_observer(const network::connection player) const { return std::find(observers_.begin(),observers_.end(),player) != observers_.end(); } -bool game::is_muted_observer(network::connection player) const -{ - return std::find(muted_observers_.begin(), muted_observers_.end(), player) != muted_observers_.end(); +bool game::is_muted_observer(const network::connection player) const { + return std::find(muted_observers_.begin(), muted_observers_.end(), player) + != muted_observers_.end(); } -bool game::is_player(network::connection player) const -{ +bool game::is_player(const network::connection player) const { return std::find(players_.begin(),players_.end(),player) != players_.end(); } -bool game::all_observers_muted() const{ - return all_observers_muted_; -} - -bool game::observers_can_label() const -{ - return false; -} - -bool game::observers_can_chat() const -{ - return true; -} - -bool game::filter_commands(network::connection player, config& cfg) -{ +bool game::filter_commands(const network::connection player, config& cfg) const { if(is_observer(player)) { std::vector marked; int index = 0; @@ -92,7 +59,7 @@ bool game::filter_commands(network::connection player, config& cfg) } else if(observers_can_chat() && (*i)->child("speak") != NULL && (*i)->all_children().size() == 1) { ; } else { - LOG_SERVER << "removing observer's illegal command\n"; + DBG_GAME << "removing observer's illegal command\n"; marked.push_back(index - marked.size()); } @@ -122,7 +89,7 @@ std::string describe_turns(int turn, const std::string& num_turns) } } -} +}//anon namespace void game::start_game() { @@ -130,16 +97,20 @@ void game::start_game() //set all side controllers to 'human' so that observers will understand that they can't //take control of any sides if they happen to have the same name as one of the descriptions - config::child_itors sides = level_.child_range("side"); - for(; sides.first != sides.second; ++sides.first) { + + for(config::child_itors sides = level_.child_range("side"); + sides.first != sides.second; ++sides.first) + { if ((**sides.first)["controller"] != "null") (**sides.first)["controller"] = "human"; } - describe_slots(); + int turn = lexical_cast_default(level()["turn_at"], 1); + end_turn_ = (turn - 1) * level_.get_children("side").size(); if(description()) { - int turn = lexical_cast_default(level()["turn_at"], 1); description()->values["turn"] = describe_turns(turn, level()["turns"]); + } else { + ERR_GAME << "ERROR: Game without description_ started. (" << id_ << ")\n"; } allow_observers_ = level_["observer"] != "no"; @@ -147,11 +118,12 @@ void game::start_game() //send [observer] tags for all observers that have already joined. //we can do this by re-joining all players that don't have sides, and aren't //the first player (game creator) - for(user_vector::const_iterator pl = observers_.begin(); pl != observers_.end(); ++pl) { + // Observer tags should have already been sent at the appropriate times. +/* for(user_vector::const_iterator pl = observers_.begin(); pl != observers_.end(); ++pl) { if(sides_.count(*pl) == 0) { add_player(*pl); } - } + }*/ } bool game::take_side(network::connection player, const config& cfg) @@ -160,21 +132,12 @@ bool game::take_side(network::connection player, const config& cfg) //verify that side is a side id const std::string& side = cfg["side"]; - size_t side_num; - try { - side_num = lexical_cast(side); - if(side_num < 1 || side_num > 9) - return false; - } - catch(bad_lexical_cast&) { - return false; - } - - size_t side_index = static_cast(side_num - 1); + size_t side_num = lexical_cast_in_range(side, 1, 1, 9); //if the side is already taken, see if we can give the player //another side instead - if(side_controllers_[side_index] == "human" || (sides_taken_[side_index] && side_controllers_[side_index] == "network")) { + if(side_controllers_[side_num - 1] == "human" + || (sides_taken_[side_num - 1] && side_controllers_[side_num - 1] == "network")) { const config::child_list& sides = level_.get_children("side"); for(config::child_list::const_iterator i = sides.begin(); i != sides.end(); ++i) { if((**i)["controller"] == "network") { @@ -188,11 +151,10 @@ bool game::take_side(network::connection player, const config& cfg) return false; } //check if the side is taken if not take it - side_index = static_cast(side_num - 1); - if(!sides_taken_[side_index]) { - side_controllers_[side_index] = "network"; - sides_.insert(std::pair(player, side_index)); - sides_taken_[side_index] = true; + if(!sides_taken_[side_num - 1]) { + side_controllers_[side_num - 1] = "network"; + sides_[side_num - 1] = player; + sides_taken_[side_num - 1] = true; config new_cfg = cfg; new_cfg["side"] = (**i)["side"]; network::queue_data(new_cfg, owner_); @@ -218,46 +180,50 @@ bool game::take_side(network::connection player, const config& cfg) //update level_ so observer who join display the good player name config::child_itors it = level_.child_range("side"); - it.first += side_index; + it.first += side_num - 1; wassert(it.first != it.second); (**it.first)["current_player"] = cfg["name"]; - side_controllers_[side_index] = cfg["controller"]; + side_controllers_[side_num - 1] = cfg["controller"]; } else - side_controllers_[side_index] = "network"; - sides_.insert(std::pair(player, side_index)); - sides_taken_[side_index] = true; + side_controllers_[side_num - 1] = "network"; + sides_[side_num - 1] = player; + sides_taken_[side_num - 1] = true; network::queue_data(cfg, owner_); return true; } -void game::update_side_data() -{ +//! Resets the side configuration according to the scenario data. +void game::update_side_data() { + // Remember everyone that is in the game. + const user_vector users = all_game_users(); + sides_taken_.clear(); sides_taken_.resize(9); sides_.clear(); + sides_.resize(9); + players_.clear(); + observers_.clear(); const config::child_itors level_sides = level_.child_range("side"); - - const user_vector users = all_game_users(); - user_vector new_players; - bool side_found; //for each player: // * Find the player name // * Find the side this player name corresponds to + //! @todo: Should iterate over sides_ then over users. for(user_vector::const_iterator player = users.begin(); player != users.end(); ++player) { player_map::const_iterator info = player_info_->find(*player); if (info == player_info_->end()) { - LOG_SERVER << "Error: unable to find player info for connection " << *player << "\n"; + ERR_GAME << "Error: unable to find player info for connection: " + << *player << "\n"; continue; } side_found = false; size_t side_num; - size_t side_index; - config::child_iterator sd; - for(sd = level_sides.first; sd != level_sides.second; ++sd) { + for(config::child_iterator sd = level_sides.first; + sd != level_sides.second; ++sd) + { try { side_num = lexical_cast((**sd)["side"]); if(side_num < 1 || side_num > 9) @@ -266,194 +232,134 @@ void game::update_side_data() catch(bad_lexical_cast&) { continue; } - side_index = static_cast(side_num - 1); - - if (!sides_taken_[side_index]){ + if (!sides_taken_[side_num - 1]){ if((**sd)["controller"] == "network") { - side_controllers_[side_index] = "network"; + side_controllers_[side_num - 1] = "network"; /*TODO : change the next line (and all related code :o) to : if((**sd)["curent_player"] == info->second.name()) { I won't make it before 1.2 is out in case of regression */ if((**sd)["user_description"] == info->second.name()) { - sides_.insert(std::pair(*player, side_index)); - sides_taken_[side_index] = true; + sides_[side_num - 1] = *player; + sides_taken_[side_num - 1] = true; side_found = true; } else { - sides_taken_[side_index] = false; + sides_taken_[side_num - 1] = false; } } else if((**sd)["controller"] == "ai") { - side_controllers_[side_index] = "ai"; - sides_taken_[side_index] = true; + sides_[side_num - 1] = owner_; + side_controllers_[side_num - 1] = "ai"; + sides_taken_[side_num - 1] = true; side_found = true; } else if((**sd)["controller"] == "human") { - sides_.insert(std::pair(owner_,side_index)); - sides_taken_[side_index] = true; + sides_[side_num - 1] = owner_; + sides_taken_[side_num - 1] = true; side_found = true; - side_controllers_[side_index] = "human"; + side_controllers_[side_num - 1] = "human"; } } } if (side_found){ - new_players.push_back(*player); + players_.push_back(*player); + } else { + observers_.push_back(*player); } } - - //check if current players are still left in observers_ and if so remove them there - //this may happen in the game lobby if an observer is put into an occupied slot - { - for(user_vector::const_iterator player = new_players.begin(); player != new_players.end(); ++player) { - user_vector::iterator u = find_connection(*player, observers_); - if (u != observers_.end()){ - observers_.erase(u); - //if the player is not in players_ add him now - if (find_connection(*player, players_) == players_.end()){ - players_.push_back(*player); - } - } - } - } - - //check if all players have a side and if not put them in observers_ - //this may happen in the game lobby if an observer is put into an occupied slot - { - //make a copy of players_ so that the iterator does not get corrupted - user_vector players_copy; - players_copy.insert(players_copy.end(), players_.begin(), players_.end()); - for (user_vector::iterator player = players_copy.begin(); player != players_copy.end(); player++){ - if (sides_.find(*player) == sides_.end()) { - //if the player doesn't have a side and is not in observers_ yet put him there - if (find_connection(*player, observers_) == observers_.end()){ - observers_.push_back(*player); - } - user_vector::iterator p = find_connection(*player, players_); - players_.erase(p); - } - } - } - - LOG_SERVER << debug_player_info(); + DBG_GAME << debug_player_info(); } -const std::string& game::transfer_side_control(const config& cfg) -{ - bool host_leave = false; - const std::string& player = cfg["player"]; +void game::transfer_side_control(const network::connection sock, const config& cfg) { + DBG_GAME << "transfer_side_control...\n"; - //find the socket for the player that is passed control - network::connection sock_entering; - bool found = false; - for (player_map::const_iterator pl = player_info_->begin(); pl != player_info_->end(); pl++){ - if (pl->second.name() == player){ - sock_entering = pl->first; - found = true; + //check, if this socket belongs to a player + const user_vector::iterator pl = find_connection(sock, players_); + if (pl == players_.end()) { + ERR_GAME << "ERROR: Not a player of this game. (socket: " << sock << ")\n"; + return; + } + const std::string& newplayer_name = cfg["player"]; + player_map::const_iterator newplayer; + for (newplayer = player_info_->begin(); newplayer != player_info_->end(); newplayer++) { + if (newplayer->second.name() == newplayer_name) { break; } } - - static const std::string notfound = "Player/Observer not found"; - if (!found) - return notfound; - - user_vector::iterator i = find_connection(sock_entering, observers_); - user_vector::iterator j = find_connection(sock_entering, players_); - - if (i == observers_.end() && j == players_.end()) - return notfound; - - + if (newplayer == player_info_->end() || !is_member(newplayer->first)) { + network::send_data(construct_server_message("Player/Observer not in this game."), sock); + return; + } const std::string& side = cfg["side"]; - static const std::string invalid = "Invalid side number"; size_t side_num; try { side_num = lexical_cast(side); - if(side_num < 1 || side_num > 9) - return invalid; + if(side_num < 1 || side_num > 9) { + network::send_data(construct_server_message( + "The side number has to be between 1 and 9."), sock); + return; + } } catch(bad_lexical_cast&) { - return invalid; + network::send_data(construct_server_message("Not a side number."), sock); + return; } const size_t nsides = level_.get_children("side").size(); - if(side_num > nsides) - return invalid; - - const size_t side_index = static_cast(side_num - 1); - - if(side_controllers_[side_index] == "network" && sides_taken_[side_index] && cfg["own_side"] != "yes") { - static const std::string already = "This side is already controlled by a player"; - return already; + if(side_num > nsides) { + network::send_data(construct_server_message("Invalid side number."), sock); + return; } - //get the socket of the player that issued the command - bool host = false; //we need to save this information before the player is erased - network::connection sock; - bool foundCommandPlayer = false; - std::multimap::iterator oldside; - for (std::multimap::iterator s = sides_.begin(); s != sides_.end(); s++){ - if (s->second == side_index){ - sock = s->first; - foundCommandPlayer = true; - oldside = s; - host = is_needed(sock); - break; - } + if(side_controllers_[side_num - 1] == "network" && sides_taken_[side_num - 1] + && cfg["own_side"] != "yes") + { + network::send_data(construct_server_message( + "This side is already controlled by a player."), sock); + return; } + // Check if the sender actually owns the side he gives away or is the host. + if (!(sides_[side_num - 1] == sock || (sock == owner_))) { + DBG_GAME << "Side belongs to: " << sides_[side_num - 1] << "\n"; + network::send_data(construct_server_message("Not your side."), sock); + return; + } + if (newplayer->first == sock) { + network::send_data(construct_server_message( + "That's already your side, silly."), sock); + return; + } + sides_[side_num - 1] = 0; + bool host_leave = false; + // If the player gave up their last side, make them an observer. + if (std::find(sides_.begin(), sides_.end(), sock) == sides_.end()) { + observers_.push_back(*pl); + players_.erase(pl); + // Tell others that the player becomes an observer. + send_data(construct_server_message(find_player(sock)->name() + + " becomes an observer.")); + // Update the client side observer list for everyone except player. + config observer_join; + observer_join.add_child("observer").values["name"] = find_player(sock)->name(); + send_data(observer_join, sock); - //The player owns this side - if(cfg["own_side"] == "yes") { - if (!foundCommandPlayer){ - static const std::string player_not_found = "This side is not listed for the game"; - return player_not_found; - } - - //check, if this socket belongs to a player - user_vector::iterator p = find_connection(sock, players_); - if (p == players_.end()){ - static const std::string no_player = "The player for this side could not be found"; - return no_player; - } - - //if a player gives up their last side, make them an observer - if (sides_.count(sock) == 1){ - observers_.push_back(*p); - players_.erase(p); - - if (sock == owner_) { - host_leave = true; - if (!players_.empty()) - owner_ = players_.front(); + + if (sock == owner_) { + host_leave = true; + if (!players_.empty()) { + owner_ = players_.front(); + send_data(construct_server_message(find_player(owner_)->name() + + " has been chosen as the new host.")); + } else { + owner_ = newplayer->first; + send_data(construct_server_message(newplayer_name + + " has been chosen as the new host.")); } - - //tell others that the player becomes an observer - config cfg_observer = construct_server_message(find_player(sock)->name() + " becomes observer"); - send_data(cfg_observer); - - //update the client observer list for everyone except player - config observer_join; - observer_join.add_child("observer").values["name"] = find_player(sock)->name(); - send_data(observer_join, sock); - - //if this player was the host of the game, transfer game control to another player - if (host && transfer_game_control() != NULL){ - const config& msg = construct_server_message(transfer_game_control()->name() + " has been chosen as new host"); - send_data(msg); - } - - //reiterate because iterators became invalid - i = find_connection(sock_entering, observers_); - j = find_connection(sock_entering, players_); } - - //clear the sides_ entry - sides_.erase(oldside); } - - side_controllers_[side_index] = "network"; - sides_taken_[side_index] = true; - sides_.insert(std::pair(sock_entering, side_index)); + side_controllers_[side_num - 1] = "network"; + sides_taken_[side_num - 1] = true; + sides_[side_num - 1] = newplayer->first; //send "change_controller" msg that make all client update //the current player name @@ -461,46 +367,46 @@ const std::string& game::transfer_side_control(const config& cfg) config& change = response.add_child("change_controller"); change["side"] = side; - change["player"] = player; - + change["player"] = newplayer_name; + // Tell everyone but the new player that this side is network controlled now. change["controller"] = "network"; - send_data(response, sock_entering); - + send_data(response, newplayer->first); + // Tell the new player that he controls this side now. change["controller"] = "human"; - network::queue_data(response, sock_entering); + network::queue_data(response, newplayer->first); - //upade level so observer who join display the good player name + // Update the level so observer who join get the new name. config::child_itors it = level_.child_range("side"); - it.first += side_index; + it.first += side_num - 1; wassert(it.first != it.second); - (**it.first)["current_player"] = player; + (**it.first)["current_player"] = newplayer_name; //if the host left and there are ai sides, transfer them to the new host if (host_leave) { - for (unsigned int i = 0; i < side_controllers_.size(); i++){ - if (side_controllers_[i] == "ai"){ + for (unsigned int i = 0; i < side_controllers_.size(); i++) { + if (side_controllers_[i] == "ai") { change["side"] = lexical_cast(i + 1); change["controller"] = "ai"; network::queue_data(response, owner_); - sides_.insert(std::pair(owner_, i)); + sides_[side_num - 1] = owner_; } } - sides_.erase(sock); } - if(sides_.count(sock_entering) < 2) { - //send everyone a message saying that the observer who is taking the side has quit + // If we gave the new side to an observer add him to players_. + const user_vector::iterator itor = std::find(observers_.begin(), + observers_.end(), newplayer->first); + if (itor != observers_.end()) { + players_.push_back(*itor); + observers_.erase(itor); + // Send everyone a message saying that the observer who is taking the + // side has quit. config observer_quit; - observer_quit.add_child("observer_quit").values["name"] = player; + observer_quit.add_child("observer_quit").values["name"] = newplayer_name; send_data(observer_quit); } - if (i != observers_.end()){ - players_.push_back(*i); - observers_.erase(i); - } - - static const std::string success = ""; - return success; + send_data(construct_server_message(newplayer_name + + " takes control of side " + side + ".")); } bool game::describe_slots() @@ -524,15 +430,13 @@ bool game::describe_slots() if(buf != (*description())["slots"]) { description()->values["slots"] = buf; - description()->values["observer"] = level_["observer"]; return true; } else { return false; } } -bool game::player_is_banned(network::connection sock) const -{ +bool game::player_is_banned(const network::connection sock) const { if(bans_.empty()) { return false; } @@ -553,7 +457,7 @@ bool game::player_is_banned(network::connection sock) const return false; } -const player* game::mute_observer(network::connection sock){ +const player* game::mute_observer(const network::connection sock) { const player* muted_observer = NULL; player_map::const_iterator it = player_info_->find(sock); if (it != player_info_->end() && ! is_muted_observer(sock)){ @@ -564,37 +468,34 @@ const player* game::mute_observer(network::connection sock){ return muted_observer; } -void game::mute_all_observers(bool mute){ - all_observers_muted_ = mute; -} - -void game::ban_player(network::connection sock) -{ +void game::ban_player(const network::connection sock) { const player_map::const_iterator itor = player_info_->find(sock); - if(itor != player_info_->end()) { + if (itor != player_info_->end()) { bans_.push_back(ban(itor->second.name(),network::ip_address(sock))); + network::send_data(construct_server_message("You have been banned."), sock); + send_data(construct_server_message(itor->second.name() + " has been banned.")); + remove_player(sock); + } else { + ERR_GAME << "ERROR: Player not found in player_info_. (socket: " << sock << ")\n"; + //! @todo: Should return something indicating the player wasn't found. } - - remove_player(sock); } -bool game::process_commands(const config& cfg) -{ - //LOG_SERVER << "processing commands: '" << cfg.write() << "'\n"; +bool game::process_commands(const config& cfg) { + //DBG_GAME << "processing commands: '" << cfg.debug() << "'\n"; bool res = false; const config::child_list& cmd = cfg.get_children("command"); for(config::child_list::const_iterator i = cmd.begin(); i != cmd.end(); ++i) { if((**i).child("end_turn") != NULL) { - res = res || end_turn(); - //LOG_SERVER << "res: " << (res ? "yes" : "no") << "\n"; + res = end_turn(); + //DBG_GAME << "res: " << (res ? "yes" : "no") << "\n"; } } return res; } -bool game::end_turn() -{ +bool game::end_turn() { //it's a new turn every time each side in the game ends their turn. ++end_turn_; @@ -616,8 +517,17 @@ bool game::end_turn() return true; } -void game::add_player(network::connection player, bool observer) -{ +void game::add_player(const network::connection player, const bool observer) { + // Hack to handle the pseudo games lobby_players_ and not_logged_in_. + if (id_ <= 2) { + observers_.push_back(player); + return; + } + //if the player is already in the game, don't add them. + if(is_member(player)) { + return; + } + //if the game has already started, we add the player as an observer if(started_) { if(!allow_observers_) { @@ -633,16 +543,14 @@ void game::add_player(network::connection player, bool observer) } //tell this player that the game has started - config cfg; - cfg.add_child("start_game"); - network::queue_data(cfg, player); + network::queue_data(config("start_game"), player); //send observer join of all the observers in the game to players for(user_vector::const_iterator ob = observers_.begin(); ob != observers_.end(); ++ob) { if(*ob != player) { info = player_info_->find(*ob); if(info != player_info_->end()) { - cfg.clear(); + config cfg; cfg.add_child("observer").values["name"] = info->second.name(); network::queue_data(cfg, player); } @@ -650,12 +558,6 @@ void game::add_player(network::connection player, bool observer) } } - //if the player is already in the game, don't add them. - user_vector users = all_game_users(); - if(std::find(users.begin(),users.end(),player) != users.end()) { - return; - } - //Check first if there are available sides //If not, add the player as observer unsigned int human_sides = 0; @@ -672,61 +574,77 @@ void game::add_player(network::connection player, bool observer) human_sides = lexical_cast(level_["human_sides"]); } } - LOG_SERVER << debug_player_info(); + DBG_GAME << debug_player_info(); if (human_sides > players_.size()){ - LOG_SERVER << "adding player...\n"; + DBG_GAME << "adding player...\n"; players_.push_back(player); } else{ - LOG_SERVER << "adding observer...\n"; + DBG_GAME << "adding observer...\n"; observers_.push_back(player); + player_map::const_iterator info = player_info_->find(player); + if(info != player_info_->end()) { + config observer_join; + observer_join.add_child("observer").values["name"] = info->second.name(); + //send observer join to everyone except player + send_data(observer_join, player); + } } - LOG_SERVER << debug_player_info(); + DBG_GAME << debug_player_info(); send_user_list(); - player_map::const_iterator info = player_info_->find(player); - if(info != player_info_->end()) { - config observer_join; - observer_join.add_child("observer").values["name"] = info->second.name(); - //send observer join to everyone except player - send_data(observer_join, player); - } //send the player the history of the game to-date network::queue_data(history_,player); } -void game::remove_player(network::connection player, bool notify_creator) -{ - LOG_SERVER << "removing player...\n"; - - bool host = false; - if (players_.size() > 0){ - host = player == owner_; - } - - { - const user_vector::iterator itor = std::find(players_.begin(),players_.end(),player); - - if(itor != players_.end()) - players_.erase(itor); - } - - { - const user_vector::iterator itor = std::find(observers_.begin(),observers_.end(),player); - - if(itor != observers_.end()) +void game::remove_player(const network::connection player, const bool notify_creator) { + // Hack to handle the pseudo games lobby_players_ and not_logged_in_. + if (id_ <= 2) { + const user_vector::iterator itor = std::find(observers_.begin(), observers_.end(), player); + if (itor != players_.end()) observers_.erase(itor); + else + DBG_GAME << "ERROR: Player is not in this game. (socket: " + << player << ")\n"; + return; } + DBG_GAME << debug_player_info(); + DBG_GAME << "removing player...\n"; - if (host && !players_.empty()) - owner_ = players_.front(); - - LOG_SERVER << debug_player_info(); + if (!is_member(player)) + return; + bool host = (player == owner_); bool observer = true; - - //check for ai sides first and drop them, too, if the host left - if (host){ + { + const user_vector::iterator itor = std::find(players_.begin(), players_.end(), player); + if (itor != players_.end()) { + players_.erase(itor); + observer = false; + } + } + { + const user_vector::iterator itor = std::find(observers_.begin(), observers_.end(), player); + if (itor != observers_.end()) { + observers_.erase(itor); + if (!observer) + ERR_GAME << "ERROR: Player is also an observer. (socket: " + << player << ")\n"; + observer = true; + } + } + // If the player was host choose a new one. + if (host && !players_.empty() && started_) { + owner_ = players_.front(); + const player_map::const_iterator host = player_info_->find(owner_); + if (host == player_info_->end()) { + ERR_GAME << "ERROR: Could not find new host in player_info_. (socket: " + << owner_ << ")\n"; + return; + } + send_data(construct_server_message(host->second.name() + + " has been chosen as new host")); + //check for ai sides first and drop them, too, if the host left bool ai_transfer = false; - //can't do this with an iterator, because it doesn't know the side_index + //can't do this with an iterator, because it doesn't know the side_num - 1 for (size_t side = 0; side < side_controllers_.size(); ++side){ //send the host a notification of removal of this side if(notify_creator && players_.empty() == false && side_controllers_[side] == "ai") { @@ -736,7 +654,6 @@ void game::remove_player(network::connection player, bool notify_creator) drop["controller"] = "ai"; network::queue_data(drop, owner_); sides_taken_[side] = false; - observer = false; } } if (ai_transfer) { @@ -746,89 +663,82 @@ void game::remove_player(network::connection player, bool notify_creator) } //look for all sides the player controlled and drop them - std::multimap::const_iterator side; - for (side = sides_.find(player); side != sides_.end(); ++side){ + for (side_vector::iterator side = sides_.begin(); side != sides_.end(); + side = std::find(side, sides_.end(), player)) + { //send the host a notification of removal of this side - if(side->first == player) { - if (notify_creator && players_.empty() == false) { - config drop; - drop["side_drop"] = lexical_cast(side->second + 1); - drop["controller"] = side_controllers_[side->second]; - network::queue_data(drop, owner_); - } - side_controllers_[side->second] = "null"; - sides_taken_[side->second] = false; - observer = false; + if (notify_creator && players_.empty() == false) { + config drop; + drop["side_drop"] = lexical_cast(side - sides_.begin() + 1); + drop["controller"] = side_controllers_[side - sides_.begin()]; + network::queue_data(drop, owner_); } + side_controllers_[side - sides_.begin()] = "null"; + sides_taken_[side - sides_.begin()] = false; + sides_[side - sides_.begin()] = 0; } - if(!observer) - sides_.erase(player); + DBG_GAME << debug_player_info(); - send_user_list(); + send_user_list(player); + if (!observer) { + return; + } - const player_map::const_iterator pl = player_info_->find(player); - if(!observer || pl == player_info_->end()) { + const player_map::const_iterator obs = player_info_->find(player); + if (obs == player_info_->end()) { + ERR_GAME << "ERROR: Could not find observer in player_info_. (socket: " + << player << ")\n"; return; } //they're just an observer, so send them having quit to clients config observer_quit; - observer_quit.add_child("observer_quit").values["name"] = pl->second.name(); + observer_quit.add_child("observer_quit").values["name"] = obs->second.name(); send_data(observer_quit); } -void game::send_user_list() -{ +void game::send_user_list(const network::connection exclude) const { //if the game hasn't started yet, then send all players a list //of the users in the game - if(started_ == false && description() != NULL) { - config cfg; - cfg.add_child("gamelist"); + if (started_ == false && description_ != NULL) { + //! @todo: Should be renamed to userlist. + config cfg("gamelist"); user_vector users = all_game_users(); for(user_vector::const_iterator p = users.begin(); p != users.end(); ++p) { - const player_map::const_iterator info = player_info_->find(*p); - if(info != player_info_->end()) { + const player_map::const_iterator pl = player_info_->find(*p); + if (pl != player_info_->end()) { config& user = cfg.add_child("user"); - user["name"] = info->second.name(); + user["name"] = pl->second.name(); } } - - send_data(cfg); + send_data(cfg, exclude); } } - -int game::id() const -{ - return id_; -} - -void game::send_data(const config& data, network::connection exclude) -{ +void game::send_data(const config& data, const network::connection exclude) const { const user_vector users = all_game_users(); for(user_vector::const_iterator i = users.begin(); i != users.end(); ++i) { - if(*i != exclude && (allow_observers_ || is_needed(*i) || sides_.count(*i) == 1)) { + if (*i != exclude) { network::queue_data(data,*i); } } } -bool game::player_on_team(const std::string& team, network::connection player) const -{ - std::pair::const_iterator, - std::multimap::const_iterator> sides = sides_.equal_range(player); - while(sides.first != sides.second) { - const config* const side_cfg = level_.find_child("side","side", lexical_cast(sides.first->second + 1)); - if(side_cfg != NULL && (*side_cfg)["team_name"] == team) { +bool game::player_on_team(const std::string& team, const network::connection player) const { + for (side_vector::const_iterator side = sides_.begin(); side != sides_.end(); + side = std::find(side, sides_.end(), player)) + { + const config* const side_cfg = level_.find_child("side", "side", + lexical_cast(side - sides_.begin() + 1)); + if (side_cfg != NULL && (*side_cfg)["team_name"] == team) { return true; } - ++sides.first; } - return false; } -void game::send_data_team(const config& data, const std::string& team, network::connection exclude) +void game::send_data_team(const config& data, const std::string& team, + const network::connection exclude) const { for(user_vector::const_iterator i = players_.begin(); i != players_.end(); ++i) { if(*i != exclude && player_on_team(team,*i)) { @@ -837,8 +747,7 @@ void game::send_data_team(const config& data, const std::string& team, network:: } } -void game::send_data_observers(const config& data, network::connection exclude) -{ +void game::send_data_observers(const config& data, const network::connection exclude) const { for(user_vector::const_iterator i = observers_.begin(); i != observers_.end(); ++i) { if (*i != exclude) { network::queue_data(data,*i); @@ -846,55 +755,22 @@ void game::send_data_observers(const config& data, network::connection exclude) } } -void game::record_data(const config& data) -{ +void game::record_data(const config& data) { history_.append(data); } -void game::reset_history() -{ +void game::reset_history() { history_.clear(); + end_turn_ = 0; } -bool game::level_init() const -{ - return level_.child("side") != NULL; -} - -config& game::level() -{ - return level_; -} - -bool game::empty() const -{ - return players_.empty() && observers_.empty(); -} - -void game::disconnect() -{ - const user_vector users = all_game_users(); - for(user_vector::const_iterator i = users.begin(); - i != users.end(); ++i) { - network::queue_disconnect(*i); - } - +void game::end_game() { + send_data(config("leave_game")); players_.clear(); observers_.clear(); } -void game::set_description(config* desc) -{ - description_ = desc; -} - -config* game::description() -{ - return description_; -} - -void game::add_players(const game& other_game, bool observer) -{ +void game::add_players(const game& other_game, const bool observer) { user_vector users = other_game.all_game_users(); if (observer){ observers_.insert(observers_.end(), users.begin(), users.end()); @@ -904,12 +780,7 @@ void game::add_players(const game& other_game, bool observer) } } -bool game::started() const -{ - return started_; -} - -const user_vector game::all_game_users() const{ +const user_vector game::all_game_users() const { user_vector res; res.insert(res.end(), players_.begin(), players_.end()); @@ -918,9 +789,11 @@ const user_vector game::all_game_users() const{ return res; } -std::string game::debug_player_info() const{ +std::string game::debug_player_info() const { std::stringstream result; + if (id_ < 3) return result.str(); result << "---------------------------------------\n"; + result << "game id: " << id_ << "\n"; result << "players_.size: " << players_.size() << "\n"; for (user_vector::const_iterator p = players_.begin(); p != players_.end(); p++){ const player_map::const_iterator user = player_info_->find(*p); @@ -952,7 +825,7 @@ std::string game::debug_player_info() const{ return result.str(); } -const player* game::find_player(network::connection sock) const{ +const player* game::find_player(const network::connection sock) const { const player* result = NULL; for (player_map::const_iterator info = player_info_->begin(); info != player_info_->end(); info++){ if (info->first == sock){ @@ -962,28 +835,13 @@ const player* game::find_player(network::connection sock) const{ return result; } -const player* game::transfer_game_control(){ - const player* result = NULL; - - //search for the next available player in the players list - if (players_.size() > 0){ - result = find_player(players_[0]); - } - return result; -} - -user_vector::iterator game::find_connection(network::connection sock, user_vector& users){ - user_vector::iterator p; - for (p = users.begin(); p != users.end(); p++){ - if ((*p) == sock){ - return p; - } - } - return users.end(); -} - -config game::construct_server_message(const std::string& message) +user_vector::iterator game::find_connection(const network::connection sock, + user_vector& users) const { + return std::find(users.begin(), users.end(), sock); +} + +config game::construct_server_message(const std::string& message) const { config turn; if(started()) { config& cmd = turn.add_child("turn"); @@ -996,6 +854,5 @@ config game::construct_server_message(const std::string& message) msg["sender"] = "server"; msg["message"] = message; } - return turn; } diff --git a/src/server/game.hpp b/src/server/game.hpp index 91b5715604c..a434673f082 100644 --- a/src/server/game.hpp +++ b/src/server/game.hpp @@ -25,113 +25,114 @@ #include #include -typedef std::vector user_vector; typedef std::map player_map; +typedef std::vector user_vector; +typedef std::vector side_vector; class game { public: game(const player_map& info); - void set_owner(const network::connection player); - bool is_owner(const network::connection player) const; - bool is_member(const network::connection player) const; - bool is_needed(const network::connection player) const; + int id() const { return id_; } + + void set_owner(const network::connection player) { owner_ = player; } + bool is_owner(const network::connection player) const { return (player == owner_); } + bool is_member(const network::connection player) const + { return is_player(player) || is_observer(player); } bool is_observer(const network::connection player) const; bool is_muted_observer(const network::connection player) const; bool is_player(const network::connection player) const; - bool all_observers_muted() const; + bool player_is_banned(const network::connection player) const; - bool observers_can_label() const; - bool observers_can_chat() const; + size_t nplayers() const { return players_.size(); } + size_t nobservers() const { return observers_.size(); } + + const player* mute_observer(network::connection player); + bool mute_all_observers() { return all_observers_muted_ = !all_observers_muted_; } + bool all_observers_muted() const { return all_observers_muted_; } + bool observers_can_label() const { return false; } + bool observers_can_chat() const { return true; } + + bool started() const { return started_; } + + bool empty() const { return players_.empty() && observers_.empty(); } //function which filters commands sent by a player to remove commands //that they don't have permission to execute. //Returns true iff there are still some commands left - bool filter_commands(network::connection player, config& cfg); + bool filter_commands(const network::connection player, config& cfg) const; void start_game(); - - bool take_side(network::connection player, const config& cfg); + //! Make everyone leave the game and clean up. + void end_game(); void update_side_data(); + bool take_side(const network::connection player, const config& cfg = config()); + //! Let's a player owning a side give it to another player or observer. + void transfer_side_control(const network::connection sock, const config& cfg); - const std::string& transfer_side_control(const config& cfg); - - //function to set the description to the number of slots - //returns true if the number of slots has changed + //! Set the description to the number of slots. + //! Returns true if the number of slots has changed. bool describe_slots(); - bool player_is_banned(network::connection player) const; - void ban_player(network::connection player); - const player* mute_observer(network::connection player); - void mute_all_observers(bool mute); + //! Ban and kick a player. He doesn't need to be in this game. + void ban_player(const network::connection player); - void add_player(network::connection player, bool observer = false); - void remove_player(network::connection player, bool notify_creator=true); + void add_player(const network::connection player, const bool observer = false); + void remove_player(const network::connection player, const bool notify_creator=true); + //adds players and observers into one vector and returns that + const user_vector all_game_users() const; - int id() const; + const player* find_player(const network::connection sock) const; - config construct_server_message(const std::string& message); - void send_data(const config& data, network::connection exclude=0); - void send_data_team(const config& data, const std::string& team, network::connection exclude=0); - void send_data_observers(const config& data, network::connection exclude=0); + //! Adds players from one game to another. This is used to add players and + //! observers from a game to the lobby (which is also implemented as a game), + //! if that game ends. The second parameter controls, wether the players are + //! added to the players_ or observers_ vector (default observers_). + void add_players(const game& other_game, const bool observer = true); + + config construct_server_message(const std::string& message) const; + void send_data(const config& data, const network::connection exclude=0) const; + void send_data_team(const config& data, const std::string& team, + const network::connection exclude=0) const; + void send_data_observers(const config& data, const network::connection exclude=0) const; void record_data(const config& data); void reset_history(); //the full scenario data - bool level_init() const; - config& level(); - bool empty() const; - void disconnect(); + bool level_init() const { return level_.child("side") != NULL; } + config& level() { return level_; } //functions to set/get the address of the game's summary description as //sent to players in the lobby - void set_description(config* desc); - config* description(); - - //adds players from one game to another. This is used to add players and - //observers from a game to the lobby (which is also implemented as a game), - //if that game ends. The second parameter controls, wether the players are - //added to the players_ or observers_ vector (default observers_). - void add_players(const game& other_game, bool observer = true); + void set_description(config* desc) { description_ = desc; } + config* description() const { return description_; } //function which will process game commands and update the state of the //game accordingly. Will return true iff the game's description changes. bool process_commands(const config& cfg); - bool started() const; - - size_t nplayers() const { return players_.size(); } - - size_t nobservers() const { return observers_.size(); } - const std::string& termination_reason() const { static const std::string aborted = "aborted"; return termination_.empty() ? aborted : termination_; } void set_termination_reason(const std::string& reason) { - if(termination_.empty()) { termination_ = reason; } + if (termination_.empty()) { termination_ = reason; } } - //adds players and observers into one vector and returns that - const user_vector all_game_users() const; - - const player* find_player(network::connection sock) const; - - const player* transfer_game_control(); - private: //returns an iterator on the users vector if sock is found - user_vector::iterator find_connection(network::connection sock, user_vector& users); + user_vector::iterator find_connection(const network::connection sock, + user_vector& users) const; //helps debugging player and observer lists std::string debug_player_info() const; //function which returns true iff 'player' is on 'team'. - bool player_on_team(const std::string& team, network::connection player) const; + bool player_on_team(const std::string& team, const network::connection player) const; //function which should be called every time a player ends their turn //(i.e. [end_turn] received). This will update the 'turn' attribute for @@ -141,7 +142,7 @@ private: //function to send a list of users to all clients. Only sends data before //the game has started. - void send_user_list(); + void send_user_list(const network::connection exclude=0) const; const player_map* player_info_; @@ -151,7 +152,7 @@ private: user_vector players_; user_vector observers_; user_vector muted_observers_; - std::multimap sides_; + side_vector sides_; std::vector sides_taken_; std::vector side_controllers_; bool started_; @@ -181,8 +182,7 @@ private: std::string termination_; }; -struct game_id_matches -{ +struct game_id_matches { game_id_matches(int id) : id_(id) {} bool operator()(const game& g) const { return g.id() == id_; } diff --git a/src/server/player.cpp b/src/server/player.cpp index 25d91cab62c..823d4ed71e6 100644 --- a/src/server/player.cpp +++ b/src/server/player.cpp @@ -16,50 +16,35 @@ #include "player.hpp" -player::player(const std::string& n, config& cfg,size_t max_messages,size_t time_period) : name_(n), cfg_(cfg), flood_start_(0), messages_since_flood_start_(0), MaxMessages(max_messages), TimePeriod(time_period) +player::player(const std::string& n, config& cfg, const size_t max_messages, const size_t time_period) + : name_(n), cfg_(cfg), flood_start_(0), messages_since_flood_start_(0), + MaxMessages(max_messages), TimePeriod(time_period) { cfg_["name"] = n; mark_available(); } // keep 'available' and game name ('location') for backward compatibility -void player::mark_available(std::string game_id, std::string location) +void player::mark_available(const std::string game_id, const std::string location) { cfg_.values["available"] = game_id.empty() ? "yes" : "no"; cfg_.values["game_id"] = game_id; cfg_.values["location"] = location; } -const std::string& player::name() const -{ - return name_; -} - -config* player::config_address() -{ - return &cfg_; -} - - -bool player::silenced() const -{ - return messages_since_flood_start_ > MaxMessages; -} - -bool player::is_message_flooding() -{ +bool player::is_message_flooding() { const time_t now = time(NULL); - if(flood_start_ == 0) { + if (flood_start_ == 0) { flood_start_ = now; return false; } ++messages_since_flood_start_; - if(now - flood_start_ > TimePeriod) { + if (now - flood_start_ > TimePeriod) { messages_since_flood_start_ = 0; flood_start_ = now; - } else if(messages_since_flood_start_ == MaxMessages) { + } else if (messages_since_flood_start_ == MaxMessages) { return true; } diff --git a/src/server/player.hpp b/src/server/player.hpp index ec2cef7a0fb..78939899552 100644 --- a/src/server/player.hpp +++ b/src/server/player.hpp @@ -24,26 +24,27 @@ class player { public: - player(const std::string& n, config& cfg,size_t max_messages=4,size_t time_period=10); + player(const std::string& n, config& cfg, const size_t max_messages=4, const size_t time_period=10); // mark a player as member of the game 'game_id' or as located in the lobby void mark_available(std::string game_id="", std::string location=""); - const std::string& name() const; - config* config_address(); + const std::string& name() const { return name_; } - bool silenced() const; + config* config_address() const { return &cfg_; } + + bool silenced() const { return messages_since_flood_start_ > MaxMessages; } bool is_message_flooding(); private: - std::string name_; + const std::string name_; config& cfg_; time_t flood_start_; unsigned int messages_since_flood_start_; - size_t MaxMessages; - time_t TimePeriod; + const size_t MaxMessages; + const time_t TimePeriod; }; #endif diff --git a/src/server/server.cpp b/src/server/server.cpp index 4a15b036cb2..f839765846c 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -53,11 +53,12 @@ // fatal and directly server related errors/warnings, // ie not caused by erroneous client data -#define ERR_SERVER LOG_STREAM(err, general) -// we abuse the warn level for normal logging -#define WRN_SERVER LOG_STREAM(warn, general) -// debugging messages -#define LOG_SERVER LOG_STREAM(info, general) +#define ERR_SERVER LOG_STREAM(err, mp_server) +#define WRN_SERVER LOG_STREAM(warn, mp_server) +#define LOG_SERVER LOG_STREAM(info, mp_server) +#define DBG_SERVER LOG_STREAM(debug, mp_server) +#define ERR_CONFIG LOG_STREAM(err, config) +#define WRN_CONFIG LOG_STREAM(warn, config) //compatibility code for MS compilers #ifndef SIGHUP @@ -81,7 +82,7 @@ config construct_error(const std::string& msg) { config construct_server_message(const std::string& message, const game& g) { config turn; - if(g.started()) { + if (g.started()) { config& cmd = turn.add_child("turn"); config& cfg = cmd.add_child("command"); config& msg = cfg.add_child("speak"); @@ -129,7 +130,7 @@ private: const std::string config_file_; config cfg_; //! Read the server config from file 'config_file_'. - config read_config(); + config read_config() const; // settings from the server config std::set accepted_versions_; @@ -145,8 +146,8 @@ private: //! Parse the server config into local variables. void load_config(); - bool ip_exceeds_connection_limit(const std::string& ip); - bool is_ip_banned(const std::string& ip); + bool ip_exceeds_connection_limit(const std::string& ip) const; + bool is_ip_banned(const std::string& ip) const; std::vector bans_; const config version_query_response_; @@ -159,19 +160,19 @@ private: metrics metrics_; time_t last_stats_; - void dump_stats(); + void dump_stats(const time_t& now); - void process_data(network::connection sock, config& data); - void process_login(network::connection sock, const config& data); + void process_data(const network::connection sock, config& data); + void process_login(const network::connection sock, const config& data); //! Handle queries from clients. - void process_query(network::connection sock, const config& query); + void process_query(const network::connection sock, const config& query); //! Process commands from admins and users. std::string process_command(const std::string& cmd); //! Handle private messages between players. - void process_whisper(const network::connection sock, const config& whisper); - void process_data_from_player_in_lobby(network::connection sock, config& data); - void process_data_from_player_in_game(network::connection sock, config& data); - void delete_game(std::vector::iterator i); + void process_whisper(const network::connection sock, const config& whisper) const; + void process_data_from_player_in_lobby(const network::connection sock, config& data); + void process_data_from_player_in_game(const network::connection sock, config& data); + void delete_game(std::vector::iterator game_it); }; server::server(int port, input_stream& input, const std::string& config_file, size_t min_threads,size_t max_threads) @@ -186,20 +187,23 @@ server::server(int port, input_stream& input, const std::string& config_file, si signal(SIGHUP, reload_config); } -config server::read_config() { +config server::read_config() const { config configuration; - if(config_file_ == "") return configuration; + if (config_file_ == "") return configuration; scoped_istream stream = istream_file(config_file_); std::string errors; try { read(configuration, *stream, &errors); - if(errors.empty() == false) { - ERR_SERVER << "WARNING: errors reading configuration file: " << errors << "\n"; + if (errors.empty()) { + WRN_CONFIG << "Server configuration from file: '" << config_file_ + << "' read.\n"; } else { - WRN_SERVER << "Server configuration from file: '" << config_file_ << "' read.\n"; + ERR_CONFIG << "ERROR: Errors reading configuration file: '" + << errors << "'.\n"; } } catch(config::error& e) { - ERR_SERVER << "ERROR: could not read configuration file: '" << config_file_ << "': '" << e.message << "'\n"; + ERR_CONFIG << "ERROR: Could not read configuration file: '" + << config_file_ << "': '" << e.message << "'.\n"; } return configuration; } @@ -207,7 +211,7 @@ config server::read_config() { void server::load_config() { admin_passwd_ = cfg_["passwd"]; motd_ = cfg_["motd"]; - if(cfg_["disallow_names"] == "") { + if (cfg_["disallow_names"] == "") { disallowed_names_.push_back("*admin*"); disallowed_names_.push_back("*admln*"); disallowed_names_.push_back("*server*"); @@ -225,9 +229,9 @@ void server::load_config() { concurrent_connections_ = lexical_cast_default(cfg_["connections_allowed"],5); const std::string& versions = cfg_["versions_accepted"]; - if(versions.empty() == false) { + if (versions.empty() == false) { const std::vector accepted(utils::split(versions)); - for(std::vector::const_iterator i = accepted.begin(); i != accepted.end(); ++i) { + for (std::vector::const_iterator i = accepted.begin(); i != accepted.end(); ++i) { accepted_versions_.insert(*i); } } else { @@ -236,26 +240,26 @@ void server::load_config() { } const config::child_list& redirects = cfg_.get_children("redirect"); - for(config::child_list::const_iterator i = redirects.begin(); i != redirects.end(); ++i) { + for (config::child_list::const_iterator i = redirects.begin(); i != redirects.end(); ++i) { const std::vector versions(utils::split((**i)["version"])); - for(std::vector::const_iterator j = versions.begin(); j != versions.end(); ++j) { + for (std::vector::const_iterator j = versions.begin(); j != versions.end(); ++j) { redirected_versions_[*j] = **i; } } const config::child_list& proxies = cfg_.get_children("proxy"); - for(config::child_list::const_iterator p = proxies.begin(); p != proxies.end(); ++p) { + for (config::child_list::const_iterator p = proxies.begin(); p != proxies.end(); ++p) { const std::vector versions(utils::split((**p)["version"])); - for(std::vector::const_iterator j = versions.begin(); j != versions.end(); ++j) { + for (std::vector::const_iterator j = versions.begin(); j != versions.end(); ++j) { proxy_versions_[*j] = **p; } } } -bool server::ip_exceeds_connection_limit(const std::string& ip) { +bool server::ip_exceeds_connection_limit(const std::string& ip) const { size_t connections = 0; - for(player_map::const_iterator i = players_.begin(); i != players_.end(); ++i) { - if(network::ip_address(i->first) == ip) { + for (player_map::const_iterator i = players_.begin(); i != players_.end(); ++i) { + if (network::ip_address(i->first) == ip) { ++connections; } } @@ -263,14 +267,14 @@ bool server::ip_exceeds_connection_limit(const std::string& ip) { return connections > concurrent_connections_; } -bool server::is_ip_banned(const std::string& ip) { - for(std::vector::const_iterator i = bans_.begin(); i != bans_.end(); ++i) { - LOG_SERVER << "comparing ban '" << *i << "' vs '" << ip << "'\t"; - if(utils::wildcard_string_match(ip,*i)) { - LOG_SERVER << "banned\n"; +bool server::is_ip_banned(const std::string& ip) const { + for (std::vector::const_iterator i = bans_.begin(); i != bans_.end(); ++i) { + DBG_SERVER << "comparing ban '" << *i << "' vs '" << ip << "'\t"; + if (utils::wildcard_string_match(ip,*i)) { + DBG_SERVER << "banned\n"; return true; } - LOG_SERVER << "not banned\n"; + DBG_SERVER << "not banned\n"; } return false; } @@ -282,46 +286,44 @@ config server::games_and_users_list_diff() { return res; } -void server::dump_stats() { - time_t old_stats = last_stats_; - last_stats_ = time(NULL); - - WRN_SERVER << - "Statistics\n" - "\tnumber_of_games = " << games_.size() << "\n" +void server::dump_stats(const time_t& now) { +// time_t old_stats = last_stats_; + last_stats_ = now; + LOG_SERVER << "Statistics:\n" + "\tnumber_of_games = " << games_.size() << "\n" "\tnumber_of_players = " << players_.size() << "\n" - "\tlobby_players = " << lobby_players_.nobservers() << "\n" - "\tstart_interval = " << old_stats << "\n" - "\tend_interval = " << last_stats_ << std::endl; + "\tlobby_players = " << lobby_players_.nobservers() << "\n"; +// "\tstart_interval = " << old_stats << "\n" +// "\tend_interval = " << last_stats_ << "\n"; } -void server::run() -{ +void server::run() { bool sync_scheduled = false; - for(int loop = 0;; ++loop) { + for (int loop = 0;; ++loop) { + SDL_Delay(20); try { - if(sync_scheduled) { + if (sync_scheduled) { // Send all players the information // that a player has logged out of the system lobby_players_.send_data(games_and_users_list_diff()); sync_scheduled = false; } - if(config_reload == 1) { + if (config_reload == 1) { cfg_ = read_config(); load_config(); config_reload = 0; } // Process commands from the server socket/fifo std::string admin_cmd; - if(input_.read_line(admin_cmd)) { - WRN_SERVER << process_command(admin_cmd) << std::endl; + if (input_.read_line(admin_cmd)) { + LOG_SERVER << process_command(admin_cmd) << std::endl; } // Make sure we log stats every 5 minutes time_t now = time(NULL); if ((loop%100) == 0 && last_stats_+5*60 < now) { - dump_stats(); + dump_stats(now); // send a 'ping' to all players to detect ghosts config ping; ping["ping"] = lexical_cast(now); @@ -333,334 +335,299 @@ void server::run() network::process_send_queue(); network::connection sock = network::accept_connection(); - if(sock) { + if (sock) { const std::string& ip = network::ip_address(sock); - if(is_ip_banned(ip)) { - WRN_SERVER << ip << "\trejected banned user.\n"; + if (is_ip_banned(ip)) { + LOG_SERVER << ip << "\trejected banned user.\n"; network::send_data(construct_error("You are banned."),sock); network::disconnect(sock); - } else if(ip_exceeds_connection_limit(ip)) { - WRN_SERVER << ip << "\trejected ip due to excessive connections\n"; + } else if (ip_exceeds_connection_limit(ip)) { + LOG_SERVER << ip << "\trejected ip due to excessive connections\n"; network::send_data(construct_error("Too many connections from your IP."),sock); network::disconnect(sock); } else { + DBG_SERVER << ip << "\tnew connection accepted. (socket: " + << sock << ")\n"; network::send_data(version_query_response_,sock); - not_logged_in_.add_player(sock); + not_logged_in_.add_player(sock, true); } } config data; - while((sock = network::receive_data(data)) != network::null_connection) { + while ((sock = network::receive_data(data)) != network::null_connection) { metrics_.service_request(); - process_data(sock,data); + process_data(sock, data); } metrics_.no_requests(); - } catch(network::error& e) { - if(!e.socket) { - ERR_SERVER << "fatal network error: " << e.message << "\n"; - break; - } else { - LOG_SERVER << "socket closed: " << e.message << "\n"; - const std::string ip = network::ip_address(e.socket); - const player_map::iterator pl_it = players_.find(e.socket); - const std::string pl_name = pl_it != players_.end() ? pl_it->second.name() : ""; - if(pl_it != players_.end()) { - const config::child_list& users = games_and_users_list_.get_children("user"); - const size_t index = std::find(users.begin(),users.end(),pl_it->second.config_address()) - users.begin(); - if(index < users.size()) - games_and_users_list_.remove_child("user",index); - - players_.erase(pl_it); - } - not_logged_in_.remove_player(e.socket); - lobby_players_.remove_player(e.socket); - for(std::vector::iterator g = games_.begin(); g != games_.end(); ++g) { - if(g->is_member(e.socket)) { - const std::string game_name = g->description() ? (*g->description())["name"] : "Warning: Game has no description."; - const bool needed = g->is_needed(e.socket); - const bool obs = g->is_observer(e.socket); - g->remove_player(e.socket); - if(obs) { - WRN_SERVER << ip << "\t" << pl_name << "\thas left game: \"" << game_name - << "\" (" << g->id() << ") as an observer and disconnected.\n"; - } else { - g->send_data(construct_server_message(pl_name + " has disconnected",*g)); - WRN_SERVER << ip << "\t" << pl_name << "\thas left game: \"" << game_name - << "\" (" << g->id() << ") and disconnected.\n"; - } - if( (g->nplayers() == 0) || (needed && !g->started()) ) { - // Tell observers the game is over, - // because the last player has left - const config cfg("leave_game"); - g->send_data(cfg); - WRN_SERVER << ip << "\t" << pl_name << "\tended game: \"" << game_name - << "\" (" << g->id() << ") and disconnected.\n"; - - // Delete the game's description - config* const gamelist = games_and_users_list_.child("gamelist"); - wassert(gamelist != NULL); - const config::child_itors vg = gamelist->child_range("game"); - - const config::child_iterator desc = std::find(vg.first,vg.second,g->description()); - if(desc != vg.second) { - gamelist->remove_child("game",desc - vg.first); - } - - // Update the state of the lobby to players in it. - // We have to sync the state of the lobby, so we can - // send it to the players leaving the game. - lobby_players_.send_data(games_and_users_list_diff()); - - //set the availability status for all quitting players - for(player_map::iterator pl = players_.begin(); pl != players_.end(); ++pl) { - if(g->is_member(pl->first)) { - pl->second.mark_available(); - } - } - - // Put the players back in the lobby, and send - // them the game list and user list again - g->send_data(games_and_users_list_); - metrics_.game_terminated(g->termination_reason()); - lobby_players_.add_players(*g); - games_.erase(g); - - // Now sync players in the lobby again, to remove the game - lobby_players_.send_data(games_and_users_list_diff()); - break; - } else if(needed) { - // Transfer game control to another player - const player* player = g->transfer_game_control(); - if (player != NULL) { - g->send_data(construct_server_message(player->name() + " has been chosen as new host", *g)); - } - // else? - - e.socket = 0; // is this needed? - break; - } - } - } - if(pl_it != players_.end()) { - WRN_SERVER << ip << "\t" << pl_name << "\thas logged off.\n"; - } - if(e.socket) { - if(proxy::is_proxy(e.socket)) { - proxy::disconnect(e.socket); - } - e.disconnect(); - } - - sync_scheduled = true; - - LOG_SERVER << "done closing socket...\n"; - } - - continue; } catch(config::error& e) { - ERR_SERVER << "error in received data: " << e.message << "\n"; - continue; - } - - SDL_Delay(20); - } -} - -void server::process_data(const network::connection sock, config& data) -{ - if(proxy::is_proxy(sock)) { - proxy::received_data(sock,data); - } - // If someone who is not yet logged in - // is sending login details - else if(not_logged_in_.is_observer(sock)) { - process_login(sock,data); - } else if(const config* query = data.child("query")) { - process_query(sock,*query); - } else if(const config* whisper = data.child("whisper")) { - process_whisper(sock,*whisper); - } else if(lobby_players_.is_observer(sock)) { - process_data_from_player_in_lobby(sock,data); - } else { - process_data_from_player_in_game(sock,data); - } -} - - -void server::process_login(const network::connection sock, const config& data) -{ - // See if client is sending their version number - const config* const version = data.child("version"); - if(version != NULL) { - const std::string& version_str = (*version)["version"]; - - bool accepted = false; - for(std::set::const_iterator ver_it = accepted_versions_.begin(); ver_it != accepted_versions_.end(); ++ver_it) { - if(utils::wildcard_string_match(version_str,*ver_it)) { - accepted = true; + WRN_CONFIG << "Warning: error in received data: " << e.message << "\n"; + } catch(network::error& e) { + if (!e.socket) { + // "Could not send initial handshake" really fatal? + ERR_SERVER << "fatal network error: " << e.message << "\n"; + throw e; break; } - } - if(accepted) { - WRN_SERVER << network::ip_address(sock) << "\tplayer joined using accepted version " << version_str << ":\ttelling them to log in.\n"; - network::send_data(login_response_,sock); - } else { - std::map::const_iterator redirect = redirected_versions_.end(); - for(std::map::const_iterator red_it = redirected_versions_.begin(); red_it != redirected_versions_.end(); ++red_it) { - if(utils::wildcard_string_match(version_str,red_it->first)) { - redirect = red_it; + DBG_SERVER << "socket closed: " << e.message << "\n"; + const std::string ip = network::ip_address(e.socket); + if (proxy::is_proxy(e.socket)) { + LOG_SERVER << ip << "\tProxy user disconnected.\n"; + proxy::disconnect(e.socket); + e.disconnect(); + DBG_SERVER << "done closing socket...\n"; + continue; + } + // Was the user already logged in? + const player_map::iterator pl_it = players_.find(e.socket); + if (pl_it == players_.end()) { + if (not_logged_in_.is_observer(e.socket)) { + WRN_SERVER << ip << "\tNot logged in user disconnected.\n"; + not_logged_in_.remove_player(e.socket); + } else { + WRN_SERVER << ip << "\tWarning: User disconnected right after the connection was accepted.\n"; + } + e.disconnect(); + DBG_SERVER << "done closing socket...\n"; + continue; + } + const config::child_list& users = games_and_users_list_.get_children("user"); + const size_t index = std::find(users.begin(), users.end(), pl_it->second.config_address()) - users.begin(); + if (index < users.size()) { + games_and_users_list_.remove_child("user",index); + } else { + ERR_SERVER << ip << "ERROR: Could not find user: " << pl_it->second.name() + << " in games_and_users_list_."; + } + sync_scheduled = true; + // Was the player in the lobby or a game? + if (lobby_players_.is_observer(e.socket)) { + lobby_players_.remove_player(e.socket); + LOG_SERVER << ip << "\t" << pl_it->second.name() << "\thas logged off.\n"; + } else { + for (std::vector::iterator g = games_.begin(); g != games_.end(); ++g) { + if (!g->is_member(e.socket)) { + continue; + } + const std::string game_name = g->description() ? (*g->description())["name"] : ""; + const bool host = g->is_owner(e.socket); + const bool obs = g->is_observer(e.socket); + g->remove_player(e.socket); + g->describe_slots(); + // Did the last player leave? + if ( (g->nplayers() == 0) || (host && !g->started()) ) { + LOG_SERVER << ip << "\t" << pl_it->second.name() + << "\tended game:\t\"" << game_name << "\" (" + << g->id() << ") and disconnected.\n"; + delete_game(g); + break; + } + if (obs) { + LOG_SERVER << ip << "\t" << pl_it->second.name() + << "\thas left game:\t\"" << game_name << "\" (" + << g->id() << ") as an observer and disconnected.\n"; + } else { + g->send_data(construct_server_message(pl_it->second.name() + " has disconnected",*g)); + LOG_SERVER << ip << "\t" << pl_it->second.name() + << "\thas left game:\t\"" << game_name << "\" (" + << g->id() << ") and disconnected.\n"; + } break; } } - if(redirect != redirected_versions_.end()) { - WRN_SERVER << network::ip_address(sock) << "\tplayer joined using version " << version_str - << ":\tredirecting them to " << redirect->second["host"] - << ":" << redirect->second["port"] << "\n"; - config response; - response.add_child("redirect",redirect->second); - network::send_data(response,sock); - } else { - std::map::const_iterator proxy = proxy_versions_.end(); - for(std::map::const_iterator prox_it = proxy_versions_.begin(); prox_it != proxy_versions_.end(); ++prox_it) { - if(utils::wildcard_string_match(version_str,prox_it->first)) { - proxy = prox_it; - break; - } - } - - if(proxy != proxy_versions_.end()) { - WRN_SERVER << network::ip_address(sock) << "\tplayer joined using version " << version_str - << ":\tconnecting them by proxy to " << proxy->second["host"] - << ":" << proxy->second["port"] << "\n"; - - proxy::create_proxy(sock,proxy->second["host"],lexical_cast_default(proxy->second["port"],15000)); - } else { - - WRN_SERVER << network::ip_address(sock) << "\tplayer joined using unknown version " << version_str - << ":\trejecting them\n"; - config response; - if(accepted_versions_.empty() == false) { - response["version"] = *accepted_versions_.begin(); - } else if(redirected_versions_.empty() == false) { - response["version"] = redirected_versions_.begin()->first; - } else { - ERR_SERVER << "This server doesn't accept any versions at all.\n"; - response["version"] = "null"; - } - - network::send_data(response,sock); - } - } + players_.erase(pl_it); + e.disconnect(); + DBG_SERVER << "done closing socket...\n"; } + } +} +void server::process_data(const network::connection sock, config& data) { + if (proxy::is_proxy(sock)) { + proxy::received_data(sock, data); + } + // If someone who is not yet logged in + // is sending login details + else if (not_logged_in_.is_observer(sock)) { + process_login(sock, data); + } else if (const config* query = data.child("query")) { + DBG_SERVER << "RECEIVED from: " << sock << ": " << data.debug(); + process_query(sock, *query); + } else if (const config* whisper = data.child("whisper")) { + process_whisper(sock, *whisper); + } else if (lobby_players_.is_observer(sock)) { + DBG_SERVER << "RECEIVED from: " << sock << ": " << data.debug(); + process_data_from_player_in_lobby(sock, data); + } else { + DBG_SERVER << "RECEIVED from: " << sock << ": " << data.debug(); + process_data_from_player_in_game(sock, data); + } +} + + +void server::process_login(const network::connection sock, const config& data) { + // See if the client is sending their version number. + if (const config* const version = data.child("version")) { + const std::string& version_str = (*version)["version"]; + std::set::const_iterator accepted_it; + // Check if it is an accepted version. + for (accepted_it = accepted_versions_.begin(); + accepted_it != accepted_versions_.end(); ++accepted_it) { + if (utils::wildcard_string_match(version_str,*accepted_it)) break; + } + if (accepted_it != accepted_versions_.end()) { + LOG_SERVER << network::ip_address(sock) + << "\tplayer joined using accepted version " << version_str + << ":\ttelling them to log in.\n"; + network::send_data(login_response_,sock); + return; + } + std::map::const_iterator config_it; + // Check if it is a redirected version + for (config_it = redirected_versions_.begin(); + config_it != redirected_versions_.end(); ++config_it) + { + if (utils::wildcard_string_match(version_str,config_it->first)) + break; + } + if (config_it != redirected_versions_.end()) { + LOG_SERVER << network::ip_address(sock) + << "\tplayer joined using version " << version_str + << ":\tredirecting them to " << config_it->second["host"] + << ":" << config_it->second["port"] << "\n"; + config response; + response.add_child("redirect",config_it->second); + network::send_data(response,sock); + return; + } + // Check if it's a version we should start a proxy for. + for (config_it = proxy_versions_.begin(); + config_it != proxy_versions_.end(); ++config_it) + { + if (utils::wildcard_string_match(version_str,config_it->first)) + break; + } + if (config_it != proxy_versions_.end()) { + LOG_SERVER << network::ip_address(sock) + << "\tplayer joined using version " << version_str + << ":\tconnecting them by proxy to " << config_it->second["host"] + << ":" << config_it->second["port"] << "\n"; + proxy::create_proxy(sock,config_it->second["host"], + lexical_cast_default(config_it->second["port"],15000)); + return; + } + // No match, send a response and reject them. + LOG_SERVER << network::ip_address(sock) + << "\tplayer joined using unknown version " << version_str + << ":\trejecting them\n"; + config response; + if (!accepted_versions_.empty()) { + response["version"] = *accepted_versions_.begin(); + } else if (redirected_versions_.empty() == false) { + response["version"] = redirected_versions_.begin()->first; + } else { + ERR_SERVER << "ERROR: This server doesn't accept any versions at all.\n"; + response["version"] = "null"; + } + network::send_data(response,sock); return; } - const config* const login = data.child("login"); - // Client must send a login first. - if(login == NULL) { - network::send_data(construct_error( - "You must login first"),sock); + if (login == NULL) { + network::send_data(construct_error("You must login first"),sock); return; } - // Check if the username is valid (all alpha-numeric plus underscore and hyphen) std::string username = (*login)["username"]; - if(!utils::isvalid_username(username)) { - network::send_data(construct_error( - "This username contains invalid characters. Only alpha-numeric characters, underscores and hyphens are allowed."),sock); + if (!utils::isvalid_username(username)) { + network::send_data(construct_error("This username contains invalid " + "characters. Only alpha-numeric characters, underscores and hyphens" + "are allowed."), sock); return; } - - if(username.size() > 18) { - network::send_data(construct_error( - "This username is too long"),sock); + if (username.size() > 18) { + network::send_data(construct_error("This username is too long"),sock); return; } - - for(std::vector::const_iterator d_it = disallowed_names_.begin(); d_it != disallowed_names_.end(); ++d_it) { - if(utils::wildcard_string_match(utils::lowercase(username),utils::lowercase(*d_it))) { - network::send_data(construct_error( - "The nick '" + username + "' is reserved and can not be used by players"),sock); + // Check if the uername is allowed. + for (std::vector::const_iterator d_it = disallowed_names_.begin(); + d_it != disallowed_names_.end(); ++d_it) + { + if (utils::wildcard_string_match(utils::lowercase(username), + utils::lowercase(*d_it))) + { + network::send_data(construct_error("The nick '" + username + + "' is reserved and can not be used by players"), sock); return; } } - // Check the username isn't already taken player_map::const_iterator p; - for(p = players_.begin(); p != players_.end(); ++p) { - if(p->second.name() == username) { - break; + for (p = players_.begin(); p != players_.end(); ++p) { + if (p->second.name() == username) { + network::send_data(construct_error("This username is already taken"), sock); + return; } } - - if(p != players_.end()) { - network::send_data(construct_error( - "This username is already taken"),sock); - return; - } - network::send_data(join_lobby_response_, sock); config* const player_cfg = &games_and_users_list_.add_child("user"); + const player new_player(username, *player_cfg, default_max_messages_, + default_time_period_); + players_.insert(std::pair(sock, new_player)); - const player new_player(username,*player_cfg,default_max_messages_,default_time_period_); - - players_.insert(std::pair(sock,new_player)); - - // Remove player from the not-logged-in list - // and place the player in the lobby not_logged_in_.remove_player(sock); - lobby_players_.add_player(sock); + lobby_players_.add_player(sock, true); // Send the new player the entire list of games and players network::send_data(games_and_users_list_,sock); - if(motd_ != "") { + if (motd_ != "") { network::send_data(construct_server_message(motd_,lobby_players_),sock); } // Send other players in the lobby the update that the player has joined lobby_players_.send_data(games_and_users_list_diff(),sock); - WRN_SERVER << network::ip_address(sock) << "\t" << username << "\thas logged on. (socket: " << sock << ")\n"; + LOG_SERVER << network::ip_address(sock) << "\t" << username + << "\thas logged on. (socket: " << sock << ")\n"; - for(std::vector::iterator g = games_.begin(); g != games_.end(); ++g) { - g->send_data_observers(construct_server_message(username + " has logged into the lobby",*g)); + for (std::vector::const_iterator g = games_.begin(); g != games_.end(); ++g) { + g->send_data_observers(construct_server_message(username + + " has logged into the lobby",*g)); } } -void server::process_query(const network::connection sock, const config& query) -{ - const player_map::iterator pl = players_.find(sock); - if(pl == players_.end()) { - LOG_SERVER << "ERROR: Could not find player socket.\n"; +void server::process_query(const network::connection sock, const config& query) { + const player_map::const_iterator pl = players_.find(sock); + if (pl == players_.end()) { + DBG_SERVER << "ERROR: Could not find player with socket: " << sock << "\n"; return; } std::string command(query["type"]); std::ostringstream response; - if(command == "status" && admins_.count(sock) == 0) { + if (command == "status" && admins_.count(sock) == 0) { command += " " + pl->second.name(); } + if (command.empty()) { // commands a player may issue - if(command == "metrics" || command == "motd" + } else if (command == "metrics" || command == "motd" || command == "status " + pl->second.name()) { response << process_command(command); - } else if(admin_passwd_.empty() == false && command == admin_passwd_) { + } else if (admin_passwd_.empty() == false && command == admin_passwd_) { admins_.insert(sock); response << "You are now recognized as an administrator"; - WRN_SERVER << "New Admin recognized:" << "\tIP: " + LOG_SERVER << "New Admin recognized:" << "\tIP: " << network::ip_address(sock) << "\tnick: " << pl->second.name() << std::endl; - } else if(admins_.count(sock) != 0) { + } else if (admins_.count(sock) != 0) { response << process_command(command); - WRN_SERVER << "Admin Command:" << "\ttype: " << command + LOG_SERVER << "Admin Command:" << "\ttype: " << command << "\tIP: "<< network::ip_address(sock) << "\tnick: "<< pl->second.name() << std::endl; - } else if(admin_passwd_.empty() == false) { + } else if (admin_passwd_.empty() == false) { WRN_SERVER << "FAILED Admin attempt:" << "\tIP: " << network::ip_address(sock) << "\tnick: " << pl->second.name() << std::endl; @@ -677,21 +644,21 @@ std::string server::process_command(const std::string& query) { const std::string command(query.begin(),i); std::string parameters = (i == query.end() ? "" : std::string(i+1,query.end())); utils::strip(parameters); - if(command == "msg" || command == "lobbymsg") { - if(parameters == "") { + if (command == "msg" || command == "lobbymsg") { + if (parameters == "") { return "You must type a message."; } - lobby_players_.send_data(construct_server_message(parameters,lobby_players_)); - if(command == "msg") { - for(std::vector::iterator g = games_.begin(); g != games_.end(); ++g) { - g->send_data(construct_server_message(parameters,*g)); + lobby_players_.send_data(construct_server_message(parameters, lobby_players_)); + if (command == "msg") { + for (std::vector::const_iterator g = games_.begin(); g != games_.end(); ++g) { + g->send_data(construct_server_message(parameters, *g)); } } - //out << "message '" << parameters << "' relayed to players\n"; - } else if(command == "status") { + out << "message '" << parameters << "' relayed to players\n"; + } else if (command == "status") { out << "STATUS REPORT\n"; - for(player_map::const_iterator pl = players_.begin(); pl != players_.end(); ++pl) { - if(parameters == "" || utils::wildcard_string_match(pl->second.name(), parameters)) { + for (player_map::const_iterator pl = players_.begin(); pl != players_.end(); ++pl) { + if (parameters == "" || utils::wildcard_string_match(pl->second.name(), parameters)) { const network::connection_stats& stats = network::get_connection_stats(pl->first); const int time_connected = stats.time_connected/1000; const int seconds = time_connected%60; @@ -703,68 +670,77 @@ std::string server::process_command(const std::string& query) { << stats.bytes_received << " bytes\n"; } } - } else if(command == "metrics") { + } else if (command == "metrics") { out << metrics_; - } else if(command == "ban" || command == "bans" || command == "kban") { - if(parameters == "") { - if(bans_.empty()) return "No bans set."; + } else if (command == "ban" || command == "bans" || command == "kban") { + if (parameters == "") { + if (bans_.empty()) return "No bans set."; out << "BAN LIST\n"; - for(std::vector::const_iterator i = bans_.begin(); i != bans_.end(); ++i) { + for (std::vector::const_iterator i = bans_.begin(); + i != bans_.end(); ++i) + { out << *i << "\n"; } } else { bool banned = false; // if we find 3 '.' consider it an ip mask - if(std::count(parameters.begin(), parameters.end(), '.') == 3) { + if (std::count(parameters.begin(), parameters.end(), '.') == 3) { banned = true; out << "Set ban on '" << parameters << "'\n"; bans_.push_back(parameters); - if(command == "kban") { - for(player_map::const_iterator pl = players_.begin(); pl != players_.end(); ++pl) { - if(utils::wildcard_string_match(network::ip_address(pl->first), parameters)) { + if (command == "kban") { + for (player_map::const_iterator pl = players_.begin(); + pl != players_.end(); ++pl) + { + if (utils::wildcard_string_match(network::ip_address(pl->first), parameters)) { network::queue_disconnect(pl->first); out << "Kicked " << pl->second.name() << ".\n"; } } } } else { - for(player_map::const_iterator pl = players_.begin(); pl != players_.end(); ++pl) { - if(utils::wildcard_string_match(pl->second.name(), parameters)) { + for (player_map::const_iterator pl = players_.begin(); + pl != players_.end(); ++pl) + { + if (utils::wildcard_string_match(pl->second.name(), parameters)) { banned = true; const std::string& ip = network::ip_address(pl->first); if (!is_ip_banned(ip)) { bans_.push_back(ip); out << "Set ban on '" << ip << "'.\n"; } - if(command == "kban") { + if (command == "kban") { network::queue_disconnect(pl->first); out << "Kicked " << pl->second.name() << ".\n"; } } } - if(!banned) { + if (!banned) { out << "Nickmask '" << parameters << "'did not match, no bans set."; } } } - } else if(command == "unban") { - if(parameters == "") { + } else if (command == "unban") { + if (parameters == "") { return "You must enter an ipmask to unban."; } - const std::vector::iterator itor = std::remove(bans_.begin(),bans_.end(),parameters); - if(itor == bans_.end()) { + const std::vector::iterator itor = + std::remove(bans_.begin(), bans_.end(), parameters); + if (itor == bans_.end()) { out << "There is no ban on '" << parameters << "'."; } else { - bans_.erase(itor,bans_.end()); + bans_.erase(itor, bans_.end()); out << "Ban on '" << parameters << "' removed."; } - } else if(command == "kick") { - if(parameters == "") { + } else if (command == "kick") { + if (parameters == "") { return "You must enter a nickmask to kick."; } bool kicked = false; - for(player_map::const_iterator pl = players_.begin(); pl != players_.end(); ++pl) { - if(utils::wildcard_string_match(pl->second.name(), parameters)) { + for (player_map::const_iterator pl = players_.begin(); + pl != players_.end(); ++pl) + { + if (utils::wildcard_string_match(pl->second.name(), parameters)) { kicked = true; const std::string name(pl->second.name()); const std::string ip(network::ip_address(pl->first)); @@ -772,46 +748,47 @@ std::string server::process_command(const std::string& query) { out << "Kicked " << name << " (" << ip << ").\n"; } } - if(!kicked) out << "No user matched '" << parameters << "'.\n"; - } else if(command == "motd") { - if(parameters == "") { - if(motd_ != "") { - out << "message of the day: " << motd_; + if (!kicked) out << "No user matched '" << parameters << "'.\n"; + } else if (command == "motd") { + if (parameters == "") { + if (motd_ != "") { + out << "Message of the day: " << motd_; return out.str(); } else { return "No message of the day set."; } } motd_ = parameters; - out << "message of the day set: " << motd_; + out << "Message of the day set to: " << motd_; } else { out << "Command '" << command << "' is not recognized.\n"; - out << "Available commands are: (lobby)msg , motd [], status [], metrics, (k)ban(s) [], unban , kick "; + out << "Available commands are: (lobby)msg , motd []" + ", status [], metrics, (k)ban(s) [], unban " + ", kick "; } return out.str(); } -void server::process_whisper(const network::connection sock, const config& whisper) -{ - const player_map::iterator pl = players_.find(sock); - if(pl == players_.end()) { - LOG_SERVER << "ERROR: Could not find player socket.\n"; +void server::process_whisper(const network::connection sock, const config& whisper) const { + const player_map::const_iterator pl = players_.find(sock); + if (pl == players_.end()) { + DBG_SERVER << "ERROR: Could not find player socket.\n"; return; } bool sent = false; bool do_send = true; - std::vector::iterator g; + std::vector::const_iterator g; if ((whisper["receiver"]!="") && (whisper["message"]!="") && (whisper["sender"]!="")) { - for(player_map::const_iterator i = players_.begin(); i != players_.end(); ++i) { - if(i->second.name() == whisper["receiver"]) { - for(g = games_.begin(); g != games_.end(); ++g) { - if(g->is_player(i->first)) { + for (player_map::const_iterator i = players_.begin(); i != players_.end(); ++i) { + if (i->second.name() == whisper["receiver"]) { + for (g = games_.begin(); g != games_.end(); ++g) { + if (g->is_player(i->first)) { do_send = false; break; } } - if(do_send == false) { + if (do_send == false) { break; } config cwhisper; @@ -834,36 +811,36 @@ void server::process_whisper(const network::connection sock, const config& whisp if (sent == false) { config msg; config data; - if(do_send == false) { + if (do_send == false) { msg["message"] = "You cannot send private messages to players in a game."; } else { msg["message"] = "Can't find player "+whisper["receiver"]; } msg["sender"] = "server"; data.add_child("message", msg); - network::send_data(data,sock); + network::send_data(data, sock); } } -void server::process_data_from_player_in_lobby(const network::connection sock, config& data) -{ - LOG_SERVER << "in process_data_from_player_in_lobby...\n"; +void server::process_data_from_player_in_lobby(const network::connection sock, config& data) { + DBG_SERVER << "in process_data_from_player_in_lobby...\n"; const player_map::iterator pl = players_.find(sock); - if(pl == players_.end()) { - LOG_SERVER << "ERROR: Could not find player socket.\n"; + if (pl == players_.end()) { + DBG_SERVER << "ERROR: Could not find player socket.\n"; return; } - if(data.child("create_game") != NULL) { + if (data.child("create_game") != NULL) { + DBG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tcreates a new game: " << (*data.child("create_game"))["name"] + << ".\n"; // Create the new game, remove the player from the lobby - // and put him/her in the game they have created + // and set the player as the host/owner. games_.push_back(game(players_)); game& g = games_.back(); g.level() = (*data.child("create_game")); - lobby_players_.remove_player(sock); g.set_owner(sock); - g.add_player(sock); // do we need this? g.level()["id"] = lexical_cast(g.id()); @@ -871,67 +848,64 @@ void server::process_data_from_player_in_lobby(const network::connection sock, c // Mark the player as unavailable in the lobby pl->second.mark_available(g.level()["id"], g.level()["name"]); + lobby_players_.remove_player(sock); lobby_players_.send_data(games_and_users_list_diff()); - return; } // See if the player is joining a game const config* const join = data.child("join"); - if(join != NULL) { + if (join != NULL) { const std::string& id = (*join)["id"]; const bool observer = ((*join)["observe"] == "yes"); const int game_id = lexical_cast(id); const std::vector::iterator g = - std::find_if(games_.begin(),games_.end(), - game_id_matches(game_id)); - if(g == games_.end()) { - LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() - << "\tattempted to join unknown game: " << id << "\n"; - const config cfg("leave_game"); - network::send_data(cfg,sock); + std::find_if(games_.begin(),games_.end(), game_id_matches(game_id)); + if (g == games_.end()) { + DBG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tattempted to join unknown game:\t" << id << "\n"; + network::send_data(config("leave_game"),sock); return; } - - if(g->player_is_banned(sock)) { - LOG_SERVER << network::ip_address(sock) << "\tReject banned player: " << pl->second.name() - << "\tfrom game: \"" << (*g->description())["name"] + const std::string game_name = g->level()["name"]; + if (g->player_is_banned(sock)) { + DBG_SERVER << network::ip_address(sock) << "\tReject banned player: " + << pl->second.name() << "\tfrom game:\t\"" << game_name << "\" (" << id << ").\n"; - network::send_data(construct_error("You are banned from this game"),sock); + network::send_data(construct_error("You are banned from this game."), sock); return; } - if(observer) { - WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name() - << "\tjoined game: \"" << (*g->description())["name"] + if (observer) { + LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tjoined game:\t\"" << game_name << "\" (" << id << ") as an observer.\n"; } else { - WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name() - << "\tjoined game: \"" << (*g->description())["name"] + LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tjoined game:\t\"" << game_name << "\" (" << id << ").\n"; } lobby_players_.remove_player(sock); - // Send them the game data - network::send_data(g->level(),sock); - + network::send_data(g->level(), sock); g->add_player(sock, observer); // Mark the player as unavailable in the lobby - pl->second.mark_available((*g->description())["id"], - (*g->description())["name"]); + pl->second.mark_available(id, game_name); lobby_players_.send_data(games_and_users_list_diff()); } // See if it's a message, in which case we add the name of the sender, // and forward it to all players in the lobby config* const message = data.child("message"); - if(message != NULL) { - if(pl->second.silenced()) { + if (message != NULL) { + if (pl->second.silenced()) { return; - } else if(pl->second.is_message_flooding()) { - network::send_data(construct_server_message("Warning: you are sending too many messages too fast. Your message has not been relayed.", - lobby_players_),pl->first); + } else if (pl->second.is_message_flooding()) { + network::send_data(construct_server_message( + "Warning: you are sending too many messages too fast. " + "Your message has not been relayed.", + lobby_players_),pl->first); return; } @@ -939,11 +913,11 @@ void server::process_data_from_player_in_lobby(const network::connection sock, c truncate_message((*message)["message"]); std::string msg = (*message)["message"].base_str(); - if(msg.substr(0,3) == "/me") { - WRN_SERVER << network::ip_address(sock) << "\t<" + if (msg.substr(0,3) == "/me") { + LOG_SERVER << network::ip_address(sock) << "\t<" << pl->second.name() << msg.substr(3) << ">\n"; } else { - WRN_SERVER << network::ip_address(sock) << "\t<" + LOG_SERVER << network::ip_address(sock) << "\t<" << pl->second.name() << "> " << msg << "\n"; } @@ -958,29 +932,33 @@ void server::process_data_from_player_in_lobby(const network::connection sock, c } } -void server::process_data_from_player_in_game(const network::connection sock, config& data) -{ - LOG_SERVER << "in process_data_from_player_in_game...\n"; +//! Process data sent by a player in a game. Note that 'data' by default gets +//! broadcasted and saved in the replay. +void server::process_data_from_player_in_game(const network::connection sock, config& data) { + DBG_SERVER << "in process_data_from_player_in_game...\n"; + bool push_immediately = true; - const player_map::iterator pl = players_.find(sock); - if(pl == players_.end()) { - LOG_SERVER << "ERROR: Could not find player socket.\n"; + if (pl == players_.end()) { + DBG_SERVER << "ERROR: Could not find player in players_. (socket: " + << sock << ")\n"; return; } std::vector::iterator g; - for(g = games_.begin(); g != games_.end(); ++g) { - if(g->is_member(sock)) + for (g = games_.begin(); g != games_.end(); ++g) { + if (g->is_owner(sock) || g->is_member(sock)) break; } - - if(g == games_.end()) { - LOG_SERVER << "ERROR: Could not find game for player: " << pl->second.name() << "\n"; + if (g == games_.end()) { + DBG_SERVER << "ERROR: Could not find game for player: " + << pl->second.name() << "\n"; return; } + const std::string game_name = g->level()["name"]; + // If this is data describing the level for a game. - if(g->is_owner(sock) && data.child("side") != NULL) { + if (g->is_owner(sock) && data.child("side") != NULL) { const bool is_init = g->level_init(); @@ -990,27 +968,10 @@ void server::process_data_from_player_in_game(const network::connection sock, co // We want to move this summary to the games_and_users_list_, and // place a pointer to that summary in the game's description. // g->level() should then receive the full data for the game. - if(!is_init) { - - // If there is no shroud, then tell players in the lobby - // what the map looks like - if((*data.child("side"))["shroud"] != "yes") { - g->level().values["map_data"] = data["map_data"]; - g->level().values["map"] = data["map"]; - } - - g->level().values["mp_era"] = data.child("era") != NULL ? data.child("era")->get_attribute("id") : ""; - g->level().values["mp_scenario"] = data["id"]; - g->level().values["mp_use_map_settings"] = data["mp_use_map_settings"]; - g->level().values["mp_village_gold"] = data["mp_village_gold"]; - g->level().values["mp_fog"] = data["mp_fog"]; - g->level().values["mp_shroud"] = data["mp_shroud"]; - g->level().values["experience_modifier"] = data["experience_modifier"]; - g->level().values["mp_countdown"] = data["mp_countdown"]; - g->level().values["mp_countdown_init_time"] = data["mp_countdown_init_time"]; - g->level().values["mp_countdown_turn_bonus"] = data["mp_countdown_turn_bonus"]; - g->level().values["mp_countdown_reservoir_time"] = data["mp_countdown_reservoir_time"]; - g->level().values["mp_countdown_action_bonus"] = data["mp_countdown_action_bonus"]; + if (!is_init) { + LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tcreated game:\t\"" << g->level()["name"] << "\" (" + << g->id() << ").\n"; // Update our config object which describes the open games, // and notifies the game of where its description is located at @@ -1019,30 +980,52 @@ void server::process_data_from_player_in_game(const network::connection sock, co config& desc = gamelist->add_child("game",g->level()); g->set_description(&desc); desc["hash"] = data["hash"]; + // If there is no shroud, then tell players in the lobby + // what the map looks like + if (data["mp_shroud"] != "yes") { + desc["map_data"] = data["map_data"]; + desc["map"] = data["map"]; + } + desc["observer"] = data["observer"]; + desc["mp_era"] = data.child("era") != NULL ? data.child("era")->get_attribute("id") : ""; + // map id + desc["mp_scenario"] = data["id"]; + desc["mp_use_map_settings"] = data["mp_use_map_settings"]; + desc["mp_village_gold"] = data["mp_village_gold"]; + desc["mp_fog"] = data["mp_fog"]; + desc["mp_shroud"] = data["mp_shroud"]; + desc["experience_modifier"] = data["experience_modifier"]; + desc["mp_countdown"] = data["mp_countdown"]; + desc["mp_countdown_init_time"] = data["mp_countdown_init_time"]; + desc["mp_countdown_turn_bonus"] = data["mp_countdown_turn_bonus"]; + desc["mp_countdown_reservoir_time"] = data["mp_countdown_reservoir_time"]; + desc["mp_countdown_action_bonus"] = data["mp_countdown_action_bonus"]; + //desc["map_name"] = data["name"]; + //desc["map_description"] = data["description"]; + //desc[""] = data["objectives"]; + //desc[""] = data["random_start_time"]; + //desc[""] = data["turns"]; + //desc["client_version"] = data["version"]; // Record the full description of the scenario in g->level() g->level() = data; + // Add the owner only once we have the level data. + g->add_player(sock); g->update_side_data(); g->describe_slots(); - WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name() << "\tcreated game: \"" - << (g->description() ? (*g->description())["name"] : "Warning: Game has no desccription.") - << "\" (" << g->id() << ").\n"; - - // Send all players in the lobby the update to the list of games + // Send all players in the lobby the update to the list of games. lobby_players_.send_data(games_and_users_list_diff()); } else { - // We've already initialized this scenario, but clobber - // its old contents with the new ones given here + // its old contents with the new ones given here. g->level() = data; g->update_side_data(); - // advancing to the next scenario? - WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name() << "\tadvanced game: \"" - << (g->description() ? (*g->description())["name"] : "Warning: Game has no description.") - << "\" (" << g->id() << ") to the next scenario.\n"; - // Send the update of the game description to the lobby - lobby_players_.send_data(games_and_users_list_diff()); + LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tadvanced game:\t\"" << g->level()["name"] << "\" (" + << g->id() << ") to the next scenario.\n"; + // Send the update of the game description to the lobby. + //lobby_players_.send_data(games_and_users_list_diff()); } // Send the new data to all players in the level (except the sender). @@ -1050,15 +1033,12 @@ void server::process_data_from_player_in_game(const network::connection sock, co return; } - const std::string game_name = g->description() ? (*g->description())["name"] : "Warning: Game has no description."; - // If this is data telling us that the scenario did change. if(g->is_owner(sock) && data.child("store_next_scenario") != NULL) { - config* scenario = data.child("store_next_scenario"); - if(g->level_init()) { g->level() = (*data.child("store_next_scenario")); g->reset_history(); + // Re-assign sides. g->update_side_data(); // Send the update of the game description to the lobby lobby_players_.send_data(games_and_users_list_diff()); @@ -1066,8 +1046,8 @@ void server::process_data_from_player_in_game(const network::connection sock, co // next_scenario sent while the scenario was not initialized. // Something's broken here. WRN_SERVER << "Warning: " << network::ip_address(sock) << "\t" - << pl->second.name() << "\tsent [next_scenario] in game: \"" - << game_name << "\" (" << g->id() + << pl->second.name() << "\tsent [next_scenario] in game:\t\"" + << g->level()["name"] << "\" (" << g->id() << ") while the scenario is not yet initialized."; return; } @@ -1078,202 +1058,128 @@ void server::process_data_from_player_in_game(const network::connection sock, co push_immediately = false; } + //! @todo: The player has already joined and got a side or not. This is + //! pointless. const string_map::const_iterator side = data.values.find("side"); - if(side != data.values.end()) { - const bool res = g->take_side(sock,data); + if (side != data.values.end()) { + const bool res = g->take_side(sock, data); config response; - if(res) { - LOG_SERVER << network::ip_address(sock) << "\t" - << pl->second.name() << "\tjoined a side in game: " + if (res) { + DBG_SERVER << network::ip_address(sock) << "\t" + << pl->second.name() << "\tjoined a side in game:\t" << game_name << "\" (" << g->id() << ").\n"; response["side_secured"] = side->second; // Update the number of available slots - const bool res = g->describe_slots(); - if(res) { + if (g->describe_slots()) { lobby_players_.send_data(games_and_users_list_diff()); } } else if (g->is_observer(sock)) { - network::send_data(construct_server_message("Sorry " + pl->second.name() + ", someone else entered before you.",*g), sock); - return; + response = construct_server_message("Sorry " + + pl->second.name() + ", someone else entered before you.",*g); } else { response["failed"] = "yes"; - LOG_SERVER << "Warning: " << network::ip_address(sock) << "\t" - << pl->second.name() << "\tfailed to get a side in game: \"" + DBG_SERVER << "Warning: " << network::ip_address(sock) << "\t" + << pl->second.name() << "\tfailed to get a side in game:\t\"" << game_name << "\" (" << g->id() << ").\n"; } - network::send_data(response,sock); return; - } - - if(data.child("start_game")) { + } else if (data.child("side_secured")) return; + else if (data.child("failed")) return; + else if (data.child("error")) return; + else if (data.child("start_game")) { // Send notification of the game starting immediately. // g->start_game() will send data that assumes // the [start_game] message has been sent g->send_data(data,sock); - WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name() - << "\tstarted game: \"" << game_name << "\" (" << g->id() << ").\n"; + LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tstarted game:\t\"" << game_name << "\" (" << g->id() << ").\n"; g->start_game(); lobby_players_.send_data(games_and_users_list_diff()); return; - } else if(data.child("leave_game")) { - const bool needed = g->is_needed(sock); + } else if (data.child("leave_game")) { + const bool host = g->is_owner(sock); const bool obs = g->is_observer(sock); g->remove_player(sock); g->describe_slots(); - - if( (g->nplayers() == 0) || (needed && !g->started()) ) { - - // Tell observers the game is over, - // because the last player has left - const config cfg("leave_game"); - g->send_data(cfg); - WRN_SERVER << network::ip_address(sock) << "\t" << pl->second.name() - << "\tended game: \"" << game_name << "\" (" << g->id() << ").\n"; - - // Delete the game's description - config* const gamelist = games_and_users_list_.child("gamelist"); - wassert(gamelist != NULL); - const config::child_itors vg = gamelist->child_range("game"); - - const config::child_iterator desc = std::find(vg.first,vg.second,g->description()); - if(desc != vg.second) { - gamelist->remove_child("game",desc - vg.first); - } - - // Update the state of the lobby to players in it. - // We have to sync the state of the lobby, - // so we can send it to the players leaving the game. - lobby_players_.send_data(games_and_users_list_diff()); - - // set the availability status for all quitting players - for(player_map::iterator pl = players_.begin(); pl != players_.end(); ++pl) { - if(g->is_member(pl->first)) { - pl->second.mark_available(); - } - } - - // Put the players back in the lobby and send them - // the game list and user list again - g->send_data(games_and_users_list_); - metrics_.game_terminated(g->termination_reason()); - lobby_players_.add_players(*g); - games_.erase(g); - - // Now sync players in the lobby again, to remove the game - lobby_players_.send_data(games_and_users_list_diff()); + if ( (g->nplayers() == 0) || (host && !g->started()) ) { + LOG_SERVER << network::ip_address(sock) << "\t" << pl->second.name() + << "\tended game:\t\"" << game_name << "\" (" << g->id() << ").\n"; + delete_game(g); } else { - if(!obs) { - g->send_data(construct_server_message(pl->second.name() + " has left the game",*g)); - WRN_SERVER << network::ip_address(sock) << "\t" - << pl->second.name() << "\thas left game: \"" + if (!obs) { + g->send_data(construct_server_message(pl->second.name() + + " has left the game",*g)); + LOG_SERVER << network::ip_address(sock) << "\t" + << pl->second.name() << "\thas left game:\t\"" << game_name << "\" (" << g->id() << ").\n"; } else { - WRN_SERVER << network::ip_address(sock) << "\t" - << pl->second.name() << "\thas left game: \"" + LOG_SERVER << network::ip_address(sock) << "\t" + << pl->second.name() << "\thas left game:\t\"" << game_name << "\" (" << g->id() << ") as an observer.\n"; } - if (needed) { - // Transfer game control to another player - const player* player = g->transfer_game_control(); - if (player != NULL) { - const config& msg = construct_server_message(player->name() + " has been chosen as new host", *g); - g->send_data(msg); - } - } } // Mark the player as available in the lobby pl->second.mark_available(); - lobby_players_.add_player(sock); + lobby_players_.add_player(sock, true); // Send the player who has quit the game list network::send_data(games_and_users_list_,sock); // Send all other players in the lobby the update to the lobby lobby_players_.send_data(games_and_users_list_diff(),sock); - return; - } else if(data.child("side_secured")) { - return; - } else if(data.child("failed")) { - return; - } - - // If this is data describing changes to a game. - else if(g->is_owner(sock) && data.child("scenario_diff")) { + // If this is data describing side changes (so far only by the host). + } else if (data.child("scenario_diff")) { g->level().apply_diff(*data.child("scenario_diff")); config* cfg_change = data.child("scenario_diff")->child("change_child"); if ((cfg_change != NULL) && (cfg_change->child("side") != NULL)) { g->update_side_data(); } - - const bool lobby_changes = g->describe_slots(); - if (lobby_changes) { + if (g->describe_slots()) { lobby_players_.send_data(games_and_users_list_diff()); } - } - - // If info is being provided about the game state - if(data.child("info") != NULL) { - const config& info = *data.child("info"); - if(info["type"] == "termination") { - g->set_termination_reason(info["condition"]); - } - } - - // If the owner is changing the controller for a side - if (data.child("change_controller") != NULL) { + // If the owner is changing the controller for a side. + } else if (data.child("change_controller") != NULL) { const config& change = *data.child("change_controller"); - // The player is either host of the game or gives away his own side - if(g->is_owner(sock) || change["own_side"] == "yes") { - const std::string& result = g->transfer_side_control(change); - if(result == "") { - g->send_data(construct_server_message(change["player"] + " takes control of side " + change["side"], *g)); - } else { - network::send_data(construct_server_message(result,*g), sock); - } - const bool lobby_changes = g->describe_slots(); - if (lobby_changes) { + // The player is either host of the game or gives away his own side. + if (g->is_owner(sock) || change["own_side"] == "yes") { + g->transfer_side_control(sock, change); + if (g->describe_slots()) { lobby_players_.send_data(games_and_users_list_diff()); } - return; } - } - - // If all observers are muted - if (g->is_owner(sock) && data.child("muteall") != NULL) { - if (!g->all_observers_muted()) { - g->mute_all_observers(true); - g->send_data(construct_server_message("all observers have been muted", *g)); + return; + // If all observers should be muted. (toggles) + } else if (g->is_owner(sock) && data.child("muteall") != NULL) { + if (g->mute_all_observers()) { + g->send_data(construct_server_message("All observers have been muted.", *g)); + } else { + g->send_data(construct_server_message("Mute of all observers has been removed.", *g)); } - else{ - g->mute_all_observers(false); - g->send_data(construct_server_message("mute of all observers is removed", *g)); - } - } - - // If an observer is muted - if(g->is_owner(sock) && data.child("mute") != NULL) { + // If an observer should be muted. + } else if (g->is_owner(sock) && data.child("mute") != NULL) { const config& u = *data.child("mute"); std::string name = u["username"]; - std::string lower_name; - lower_name.resize(name.size()); - std::transform(name.begin(), name.end(), lower_name.begin(), tolower); - + //! @todo All this processing should be done in mute_observer(). if (!name.empty()) { - player_map::iterator pl; - for(pl = players_.begin(); pl != players_.end(); ++pl) { - if(pl->second.name() == name) { + player_map::const_iterator pl; + for (pl = players_.begin(); pl != players_.end(); ++pl) { + if (pl->second.name() == name) { break; } } - if(pl->first != sock && pl != players_.end() && g->is_observer(pl->first)) { + if (pl != players_.end() && pl->first != sock + && pl != players_.end() && g->is_observer(pl->first)) + { const player* player = g->mute_observer(pl->first); if (player != NULL) { - network::send_data(construct_server_message("You have been muted", *g), pl->first); - g->send_data(construct_server_message(pl->second.name() + " has been muted", *g), pl->first); + network::send_data(construct_server_message( + "You have been muted.", *g), pl->first); + g->send_data(construct_server_message(pl->second.name() + + " has been muted.", *g), pl->first); } } } @@ -1282,7 +1188,9 @@ void server::process_data_from_player_in_game(const network::connection sock, co user_vector users = g->all_game_users(); const player* player; - for (user_vector::const_iterator user = users.begin(); user != users.end(); user++) { + for (user_vector::const_iterator user = users.begin(); + user != users.end(); user++) + { if ((g->all_observers_muted() && g->is_observer(*user)) || (!g->all_observers_muted() && g->is_muted_observer(*user))) { player = g->find_player(*user); @@ -1292,19 +1200,18 @@ void server::process_data_from_player_in_game(const network::connection sock, co } } } - g->send_data(construct_server_message("muted observers: " + muted_nicks, *g)); + g->send_data(construct_server_message("Muted observers: " + + muted_nicks, *g)); } - } - // The owner is kicking/banning someone from the game. - if(g->is_owner(sock) && (data.child("ban") != NULL || data.child("kick") != NULL)) { + } else if (g->is_owner(sock) && (data.child("ban") || data.child("kick"))) { std::string name; bool ban = false; if (data.child("ban") != NULL) { const config& u = *data.child("ban"); name = u["username"]; ban = true; - } else if (data.child("kick") != NULL) { + } else { const config& u = *data.child("kick"); name = u["username"]; ban = false; @@ -1314,71 +1221,69 @@ void server::process_data_from_player_in_game(const network::connection sock, co // new player_map iterator that masks the one of the sender player_map::iterator pl; - for(pl = players_.begin(); pl != players_.end(); ++pl) { - if(pl->second.name() == name) { + for (pl = players_.begin(); pl != players_.end(); ++pl) { + if (pl->second.name() == name) { break; } } - if(pl != players_.end() && pl->first != sock) { - // is the player even in this game? - if(!g->is_member(pl->first)) return; - + if (pl != players_.end() && pl->first != sock) { if (ban) { - network::send_data(construct_server_message("You have been banned", *g), pl->first); - g->send_data(construct_server_message(name + " has been banned", *g)); + LOG_SERVER << network::ip_address(sock) << "\t" + << owner << "\tbanned: " << name << "\tfrom game:\t" + << game_name << "\" (" << g->id() << ")\n"; g->ban_player(pl->first); - WRN_SERVER << network::ip_address(sock) << "\t" - << owner << "\tbanned: " << name << "\tfrom game: " - << game_name << "\" (" << g->id() << ")\n"; } else { - network::send_data(construct_server_message("You have been kicked", *g), pl->first); - g->send_data(construct_server_message(name + " has been kicked", *g)); - g->remove_player(pl->first); - WRN_SERVER << network::ip_address(sock) << "\t" - << owner << "\tkicked: " << name << " from game: \"" + LOG_SERVER << network::ip_address(sock) << "\t" + << owner << "\tkicked: " << name << " from game:\t\"" << game_name << "\" (" << g->id() << ")\n"; + network::send_data(construct_server_message( + "You have been kicked.", *g), pl->first); + g->send_data(construct_server_message(name + " has been kicked.", *g)); + g->remove_player(pl->first); } - - const config leave_game("leave_game"); - network::send_data(leave_game, pl->first); + network::send_data(config("leave_game"), pl->first); g->describe_slots(); - lobby_players_.add_player(pl->first); - - // Mark the player as available in the lobby + lobby_players_.add_player(pl->first, true); + // Mark the player as available in the lobby. pl->second.mark_available(); - - // Send the player who was banned the lobby game list + // Send the player who was banned the lobby game list. network::send_data(games_and_users_list_, pl->first); - - // Send all other players in the lobby the update to the lobby + // Send all other players in the lobby the update to the lobby. lobby_players_.send_data(games_and_users_list_diff(), sock); return; - } else if(pl == players_.end()) { - network::send_data(construct_server_message("Kick/ban failed: user '" + name + "' not found", *g), sock); + } else if (pl == players_.end()) { + network::send_data(construct_server_message("Kick/ban failed: user '" + + name + "' not found.", *g), sock); return; } - } else if(data.child("ban")) { + } else if (data.child("ban")) { const config& response = construct_server_message( - "You cannot ban: not the game host", *g); + "You cannot ban: not the game host.", *g); network::send_data(response,sock); return; - } else if(data.child("kick")) { + } else if (data.child("kick")) { const config& response = construct_server_message( - "You cannot kick: not the game host", *g); + "You cannot kick: not the game host.", *g); network::send_data(response,sock); return; + // If info is being provided about the game state. + } else if (data.child("info") != NULL) { + const config& info = *data.child("info"); + if (info["type"] == "termination") { + g->set_termination_reason(info["condition"]); + } } config* const turn = data.child("turn"); - if(turn != NULL) { + if (turn != NULL) { g->filter_commands(sock, *turn); // Notify the game of the commands, and if it changes // the description, then sync the new description - // to players in the lobby + // to players in the lobby. const bool res = g->process_commands(*turn); - if(res) { + if (res) { lobby_players_.send_data(games_and_users_list_diff()); } @@ -1388,14 +1293,18 @@ void server::process_data_from_player_in_game(const network::connection sock, co const config::child_itors speaks = turn->child_range("command"); int npublic = 0, nprivate = 0, nother = 0; std::string team_name; - for(config::child_iterator i = speaks.first; i != speaks.second; ++i) { + for (config::child_iterator i = speaks.first; i != speaks.second; ++i) { config* const speak = (*i)->child("speak"); - if(speak == NULL) { + if (speak == NULL) { ++nother; continue; } - if ((g->all_observers_muted() && g->is_observer(sock)) || g->is_muted_observer(sock)) { - network::send_data(construct_server_message("You have been muted, others can't see your message!", *g), pl->first); + if ((g->all_observers_muted() && g->is_observer(sock)) + || g->is_muted_observer(sock)) + { + network::send_data(construct_server_message( + "You have been muted, others can't see your message!", *g), + pl->first); return; } truncate_message((*speak)["message"]); @@ -1404,7 +1313,7 @@ void server::process_data_from_player_in_game(const network::connection sock, co // to prevent spoofing of messages (*speak)["description"] = pl->second.name(); - if((*speak)["team_name"] == "") { + if ((*speak)["team_name"] == "") { ++npublic; } else { ++nprivate; @@ -1414,7 +1323,7 @@ void server::process_data_from_player_in_game(const network::connection sock, co // If all there are are messages and they're all private, then // just forward them on to the client that should receive them. - if(nprivate > 0 && npublic == 0 && nother == 0) { + if (nprivate > 0 && npublic == 0 && nother == 0) { if (team_name == game_config::observer_team_name) { g->send_data_observers(data, sock); } else { @@ -1429,14 +1338,13 @@ void server::process_data_from_player_in_game(const network::connection sock, co // respect not displaying the message. } - // If a player advances to the next scenario of a mp campaign + // If a player advances to the next scenario of a mp campaign. if(data.child("notify_next_scenario") != NULL) { - const config& info = *data.child("notify_next_scenario"); - const std::string player_name = g->find_player(sock)->name(); - g->send_data(construct_server_message(player_name + " advanced to the next scenario", *g), sock); + g->send_data(construct_server_message(pl->second.name() + + " advanced to the next scenario.", *g), sock); } - //A mp client sends a request for the next scenario of a mp campaign. + // A mp client sends a request for the next scenario of a mp campaign. if (data.child("load_next_scenario") != NULL) { config cfg_scenario; @@ -1449,34 +1357,45 @@ void server::process_data_from_player_in_game(const network::connection sock, co // Forward data to all players who are in the game, // except for the original data sender + // FIXME: Relaying arbitrary data that possibly didn't get handled at all + // seems like a bad idea. if (push_immediately) g->send_data(data,sock); - if(g->started()) { + if (g->started()) { g->record_data(data); } } -void server::delete_game(std::vector::iterator i) -{ - metrics_.game_terminated(i->termination_reason()); - - // Delete the game's configuration +void server::delete_game(std::vector::iterator game_it) { + metrics_.game_terminated(game_it->termination_reason()); + // Delete the game from the games_and_users_list_. config* const gamelist = games_and_users_list_.child("gamelist"); wassert(gamelist != NULL); - const config::child_itors vg = gamelist->child_range("game"); - const config::child_list::iterator g = std::find(vg.first, vg.second, i->description()); - if(g != vg.second) { - const size_t index = g - vg.first; + const config::child_itors games = gamelist->child_range("game"); + const config::child_list::const_iterator g = + std::find(games.first, games.second, game_it->description()); + if (g != games.second) { + const size_t index = g - games.first; gamelist->remove_child("game", index); + } else { + ERR_SERVER << "ERROR: Could not find game to delete in games_and_users_list_."; } - - i->disconnect(); - games_.erase(i); + //set the availability status for all quitting players + for (player_map::iterator pl = players_.begin(); pl != players_.end(); pl++) { + if (game_it->is_member(pl->first)) { + pl->second.mark_available(); + } + } + // Put the players back in the lobby, and send + // them the games_and_users_list_ again. + game_it->send_data(games_and_users_list_); + lobby_players_.add_players(*game_it); + game_it->end_game(); + games_.erase(game_it); } -int main(int argc, char** argv) -{ +int main(int argc, char** argv) { int port = 15000; size_t min_threads = 5; size_t max_threads = 0; @@ -1488,19 +1407,48 @@ int main(int argc, char** argv) #endif std::string fifo_path = std::string(FIFODIR) + "/socket"; - for(int arg = 1; arg != argc; ++arg) { + // show 'info' by default + lg::set_log_domain_severity("server", 2); + lg::timestamps(true); + + for (int arg = 1; arg != argc; ++arg) { const std::string val(argv[arg]); - if(val.empty()) { + if (val.empty()) { continue; } - if((val == "--config" || val == "-c") && arg+1 != argc) { + if ((val == "--config" || val == "-c") && arg+1 != argc) { config_file = argv[++arg]; - } else if(val == "--verbose" || val == "-v") { - lg::set_log_domain_severity("all",2); - } else if((val == "--port" || val == "-p") && arg+1 != argc) { + } else if (val == "--verbose" || val == "-v") { + lg::set_log_domain_severity("all",3); + } else if (val.substr(0, 6) == "--log-") { + size_t p = val.find('='); + if (p == std::string::npos) { + std::cerr << "unknown option: " << val << '\n'; + return 0; + } + std::string s = val.substr(6, p - 6); + int severity; + if (s == "error") severity = 0; + else if (s == "warning") severity = 1; + else if (s == "info") severity = 2; + else if (s == "debug") severity = 3; + else { + std::cerr << "unknown debug level: " << s << '\n'; + return 0; + } + while (p != std::string::npos) { + size_t q = val.find(',', p + 1); + s = val.substr(p + 1, q == std::string::npos ? q : q - (p + 1)); + if (!lg::set_log_domain_severity(s, severity)) { + std::cerr << "unknown debug domain: " << s << '\n'; + return 0; + } + p = q; + } + } else if ((val == "--port" || val == "-p") && arg+1 != argc) { port = atoi(argv[++arg]); - } else if(val == "--help" || val == "-h") { + } else if (val == "--help" || val == "-h") { std::cout << "usage: " << argv[0] << " [-dvV] [-c path] [-m n] [-p port] [-t n]\n" << " -c --config path Tells wesnothd where to find the config file to use.\n" @@ -1511,50 +1459,48 @@ int main(int argc, char** argv) << " -v --verbose Turns on more verbose logging.\n" << " -V, --version Returns the server version.\n"; return 0; - } else if(val == "--version" || val == "-V") { + } else if (val == "--version" || val == "-V") { std::cout << "Battle for Wesnoth server " << game_config::version << "\n"; return 0; - } else if(val == "--daemon" || val == "-d") { + } else if (val == "--daemon" || val == "-d") { #ifdef WIN32 ERR_SERVER << "Running as a daemon is not supported on this platform\n"; return -1; #else const pid_t pid = fork(); - if(pid < 0) { + if (pid < 0) { ERR_SERVER << "Could not fork and run as a daemon\n"; return -1; - } else if(pid > 0) { - std::cout << "Started wesnothd as a daemon with process id " << pid << "\n"; + } else if (pid > 0) { + std::cout << "Started wesnothd as a daemon with process id " + << pid << "\n"; return 0; } setsid(); #endif - } else if((val == "--threads" || val == "-t") && arg+1 != argc) { + } else if ((val == "--threads" || val == "-t") && arg+1 != argc) { min_threads = atoi(argv[++arg]); - if(min_threads > 30) { + if (min_threads > 30) { min_threads = 30; } - } else if((val == "--max-threads" || val == "-T") && arg+1 != argc) { + } else if ((val == "--max-threads" || val == "-T") && arg+1 != argc) { max_threads = atoi(argv[++arg]); - } else if(val[0] == '-') { + } else if (val[0] == '-') { ERR_SERVER << "unknown option: " << val << "\n"; return 0; } else { port = atoi(argv[arg]); } } - // show 'warnings' by default - lg::set_log_domain_severity("general", 1); - lg::timestamps(true); - input_stream input(fifo_path); try { server(port, input, config_file, min_threads, max_threads).run(); } catch(network::error& e) { - ERR_SERVER << "caught network error while server was running. aborting.: " << e.message << "\n"; + ERR_SERVER << "caught network error while server was running. aborting.: " + << e.message << "\n"; return -1; }