refactor of server code

This commit is contained in:
Dave White 2005-02-20 21:37:14 +00:00
parent 6eb0df334a
commit caf6d9cf5f

View file

@ -64,6 +64,13 @@ public:
server(int port, input_stream& input);
void run();
private:
void process_data(network::connection sock, config& data, config& gamelist);
void process_login(network::connection sock, const config& data, config& gamelist);
void process_query(network::connection sock, const config& query, config& gamelist);
void process_data_from_player_in_lobby(network::connection sock, config& data, config& gamelist);
void process_data_from_player_in_game(network::connection sock, config& data, config& gamelist);
void process_command(const std::string& cmd);
void delete_game(std::vector<game>::iterator i);
@ -279,468 +286,7 @@ void server::run()
while((sock = network::receive_data(data)) != network::null_connection) {
metrics_.service_request();
//if someone who is not yet logged in is sending
//login details
if(not_logged_in_.is_member(sock)) {
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);
continue;
}
//check the username is valid (all alpha-numeric or space)
std::string username = (*login)["username"];
config::strip(username);
const int alnum = std::count_if(username.begin(),username.end(),isalnum);
const int spaces = std::count(username.begin(),username.end(),' ');
if((alnum + spaces != username.size()) || spaces == username.size() || username.empty()) {
network::send_data(construct_error(
"This username is not valid"),sock);
continue;
}
if(username.size() > 32) {
network::send_data(construct_error(
"This username is too long"),sock);
continue;
}
if(username == "server") {
network::send_data(construct_error(
"The nick 'server' is reserved and can not be used by players"),sock);
continue;
}
//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;
}
}
if(p != players_.end()) {
network::send_data(construct_error(
"This username is already taken"),sock);
continue;
}
config* const player_cfg = &initial_response_.add_child("user");
const player new_player(username,*player_cfg);
players_.insert(std::pair<network::connection,player>(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);
//send the new player the entire list of games and players
network::send_data(initial_response_,sock);
//send other players in the lobby the update that the player has joined
lobby_players_.send_data(sync_initial_response(),sock);
std::cerr << "'" << username << "' (" << network::ip_address(sock) << ") has logged on\n";
for(std::vector<game>::iterator g = games_.begin(); g != games_.end(); ++g) {
g->send_data_observers(construct_server_message(username + " has logged into the lobby",*g));
}
} else if(const config* query = data.child("query")) {
//process queries from clients in here
std::ostringstream response;
if((*query)["type"] == "metrics") {
//a query for server data from a player
response << metrics_;
} else {
response << "Error: unrecognized query";
}
network::send_data(construct_server_message(response.str(),lobby_players_),sock);
} else if(lobby_players_.is_member(sock)) {
const config* const create_game = data.child("create_game");
if(create_game != NULL) {
std::cerr << "creating game...\n";
//create the new game, remove the player from the
//lobby and put him/her in the game they have created
games_.push_back(game(players_));
lobby_players_.remove_player(sock);
games_.back().add_player(sock);
//store the game data here at the moment
games_.back().level() = *create_game;
std::stringstream converter;
converter << games_.back().id();
games_.back().level()["id"] = converter.str();
//mark the player as unavailable in the lobby
const player_map::iterator pl = players_.find(sock);
if(pl != players_.end()) {
pl->second.mark_available(false);
lobby_players_.send_data(sync_initial_response());
} else {
std::cerr << "ERROR: Could not find player in map\n";
}
continue;
}
//see if the player is joining a game
const config* const join = data.child("join");
if(join != NULL) {
const std::string& id = (*join)["id"];
const int nid = atoi(id.c_str());
const std::vector<game>::iterator it =
std::find_if(games_.begin(),games_.end(),
game_id_matches(nid));
if(it == games_.end()) {
//send a response saying the game has been cancelled
config cfg;
cfg.add_child("leave_game");
network::send_data(cfg,sock);
std::cerr << "attempt to join unknown game\n";
continue;
}
if(it->player_is_banned(sock)) {
network::send_data(construct_error("You are banned from this game"),sock);
continue;
}
lobby_players_.remove_player(sock);
//send them the game data
network::send_data(it->level(),sock);
it->add_player(sock);
//mark the player as unavailable in the lobby
const player_map::iterator pl = players_.find(sock);
if(pl != players_.end()) {
pl->second.mark_available(false);
lobby_players_.send_data(sync_initial_response());
} else {
std::cerr << "ERROR: Could not find player in map\n";
}
}
//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) {
const player_map::const_iterator p = players_.find(sock);
wassert(p != players_.end());
(*message)["sender"] = p->second.name();
truncate_message((*message)["message"]);
lobby_players_.send_data(data,sock);
}
} else {
std::vector<game>::iterator g;
for(g = games_.begin(); g != games_.end(); ++g) {
if(g->is_member(sock))
break;
}
if(g == games_.end()) {
std::cerr << "ERROR: unknown socket " << games_.size() << "\n";
continue;
}
//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(g->is_owner(sock) && data.child("change_controller") != NULL) {
const config& change = *data.child("change_controller");
const std::string& result = g->transfer_side_control(change);
if(result == "") {
const config& msg = construct_server_message(change["player"] + " takes control of side " + change["side"],*g);
g->send_data(msg);
} else {
const config& msg = construct_server_message(result,*g);
network::send_data(msg,sock);
}
continue;
}
//if the owner is banning someone from the game
if(g->is_owner(sock) && data.child("ban") != NULL) {
const config& ban = *data.child("ban");
const std::string& name = ban["username"];
player_map::iterator pl;
for(pl = players_.begin(); pl != players_.end(); ++pl) {
if(pl->second.name() == name) {
break;
}
}
if(pl->first != sock && pl != players_.end()) {
g->ban_player(pl->first);
const config& msg = construct_server_message("You have been banned",*g);
network::send_data(msg,pl->first);
config leave_game;
leave_game.add_child("leave_game");
network::send_data(leave_game,pl->first);
g->describe_slots();
lobby_players_.add_player(pl->first);
//mark the player as available in the lobby
pl->second.mark_available(true);
//send the player who was banned the lobby game list
network::send_data(initial_response_,pl->first);
//send all other players in the lobby the update to the lobby
lobby_players_.send_data(sync_initial_response(),sock);
} else if(pl == players_.end()) {
const config& response = construct_server_message(
"Ban failed: user '" + name + "' not found",*g);
network::send_data(response,sock);
}
} else if(data.child("ban")) {
const config& response = construct_server_message(
"You cannot ban: not the game creator",*g);
network::send_data(response,sock);
}
//if this is data describing changes to a game.
else if(g->is_owner(sock) && data.child("scenario_diff")) {
g->level().apply_diff(*data.child("scenario_diff"));
g->send_data(data,sock);
const bool lobby_changes = g->describe_slots();
if(lobby_changes) {
lobby_players_.send_data(sync_initial_response());
}
}
//if this is data describing the level for a game.
else if(g->is_owner(sock) && data.child("side") != NULL) {
const bool is_init = g->level_init();
//if this game is having its level data initialized
//for the first time, and is ready for players to join.
//We should currently have a summary of the game in g->level().
//we want to move this summary to the initial_response_, 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"];
}
//update our config object which describes the
//open games, and notifies the game of where its description
//is located at
config& desc = gamelist.add_child("game",g->level());
g->set_description(&desc);
//record the full description of the scenario in g->level()
g->level() = data;
g->describe_slots();
//send all players in the lobby the update to the list of games
lobby_players_.send_data(sync_initial_response());
} else {
//we've already initialized this scenario, but clobber its old
//contents with the new ones given here
g->level() = data;
}
//send all players in the level, except the sender, the new data
g->send_data(data,sock);
continue;
}
const string_map::const_iterator side = data.values.find("side");
if(side != data.values.end()) {
const bool res = g->take_side(sock,data);
config response;
if(res) {
std::cerr << "played joined side\n";
response["side_secured"] = side->second;
//update the number of available slots
const bool res = g->describe_slots();
if(res) {
lobby_players_.send_data(sync_initial_response());
}
} else {
response["failed"] = "yes";
}
network::send_data(response,sock);
continue;
}
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);
g->start_game();
lobby_players_.send_data(sync_initial_response());
continue;
} else if(data.child("leave_game")) {
const bool needed = g->is_needed(sock);
if(needed) {
//tell all other players the game is over,
//because a needed player has left
config cfg;
cfg.add_child("leave_game");
g->send_data(cfg);
//delete the game's description
config* const gamelist = initial_response_.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(sync_initial_response());
//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(true);
}
}
//put the players back in the lobby and send
//them the game list and user list again
g->send_data(initial_response_);
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(sync_initial_response());
} else {
g->remove_player(sock);
g->describe_slots();
lobby_players_.add_player(sock);
//mark the player as available in the lobby
const player_map::iterator pl = players_.find(sock);
if(pl != players_.end()) {
pl->second.mark_available(true);
} else {
std::cerr << "ERROR: Could not find player in map\n";
}
//send the player who has quit the game list
network::send_data(initial_response_,sock);
//send all other players in the lobby the update to the lobby
lobby_players_.send_data(sync_initial_response(),sock);
}
continue;
} else if(data["side_secured"].empty() == false) {
continue;
} else if(data["failed"].empty() == false) {
std::cerr << "ERROR: failure to get side\n";
continue;
}
config* const turn = data.child("turn");
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
const bool res = g->process_commands(*turn);
if(res) {
lobby_players_.send_data(sync_initial_response());
}
//any private 'speak' commands must be repackaged seperate
//to other commands, and re-sent, since they should only go
//to some clients.
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) {
config* const speak = (*i)->child("speak");
if(speak == NULL) {
++nother;
continue;
}
truncate_message((*speak)["message"]);
//force the description to be correct to prevent
//spoofing of messages
const player_map::const_iterator pl = players_.find(sock);
wassert(pl != players_.end());
(*speak)["description"] = pl->second.name();
if((*speak)["team_name"] == "") {
++npublic;
} else {
++nprivate;
team_name = (*speak)["team_name"];
}
}
//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) {
g->send_data_team(data,team_name,sock);
continue;
}
//at the moment, if private messages are mixed in with other
//data, then let them go through. It's exceedingly unlikely that
//this will happen anyway, and if it does, the client should
//respect not displaying the message.
}
//forward data to all players who are in the game,
//except for the original data sender
g->send_data(data,sock);
if(g->started()) {
g->record_data(data);
}
}
process_data(sock,data,gamelist);
}
metrics_.no_requests();
@ -793,6 +339,489 @@ void server::run()
}
}
void server::process_data(const network::connection sock, config& data, config& gamelist)
{
//if someone who is not yet logged in is sending
//login details
if(not_logged_in_.is_member(sock)) {
process_login(sock,data,gamelist);
} else if(const config* query = data.child("query")) {
process_query(sock,*query,gamelist);
} else if(lobby_players_.is_member(sock)) {
process_data_from_player_in_lobby(sock,data,gamelist);
} else {
process_data_from_player_in_game(sock,data,gamelist);
}
}
void server::process_login(const network::connection sock, const config& data, config& gamelist)
{
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);
return;
}
//check the username is valid (all alpha-numeric or space)
std::string username = (*login)["username"];
config::strip(username);
const int alnum = std::count_if(username.begin(),username.end(),isalnum);
const int spaces = std::count(username.begin(),username.end(),' ');
if((alnum + spaces != username.size()) || spaces == username.size() || username.empty()) {
network::send_data(construct_error(
"This username is not valid"),sock);
return;
}
if(username.size() > 32) {
network::send_data(construct_error(
"This username is too long"),sock);
return;
}
if(username == "server") {
network::send_data(construct_error(
"The nick 'server' 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;
}
}
if(p != players_.end()) {
network::send_data(construct_error(
"This username is already taken"),sock);
return;
}
config* const player_cfg = &initial_response_.add_child("user");
const player new_player(username,*player_cfg);
players_.insert(std::pair<network::connection,player>(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);
//send the new player the entire list of games and players
network::send_data(initial_response_,sock);
//send other players in the lobby the update that the player has joined
lobby_players_.send_data(sync_initial_response(),sock);
std::cerr << "'" << username << "' (" << network::ip_address(sock) << ") has logged on\n";
for(std::vector<game>::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, config& gamelist)
{
//process queries from clients in here
std::ostringstream response;
if(query["type"] == "metrics") {
//a query for server data from a player
response << metrics_;
} else {
response << "Error: unrecognized query";
}
network::send_data(construct_server_message(response.str(),lobby_players_),sock);
}
void server::process_data_from_player_in_lobby(const network::connection sock, config& data, config& gamelist)
{
const config* const create_game = data.child("create_game");
if(create_game != NULL) {
std::cerr << "creating game...\n";
//create the new game, remove the player from the
//lobby and put him/her in the game they have created
games_.push_back(game(players_));
lobby_players_.remove_player(sock);
games_.back().add_player(sock);
//store the game data here at the moment
games_.back().level() = *create_game;
std::stringstream converter;
converter << games_.back().id();
games_.back().level()["id"] = converter.str();
//mark the player as unavailable in the lobby
const player_map::iterator pl = players_.find(sock);
if(pl != players_.end()) {
pl->second.mark_available(false);
lobby_players_.send_data(sync_initial_response());
} else {
std::cerr << "ERROR: Could not find player in map\n";
}
return;
}
//see if the player is joining a game
const config* const join = data.child("join");
if(join != NULL) {
const std::string& id = (*join)["id"];
const int nid = atoi(id.c_str());
const std::vector<game>::iterator it =
std::find_if(games_.begin(),games_.end(),
game_id_matches(nid));
if(it == games_.end()) {
//send a response saying the game has been cancelled
config cfg;
cfg.add_child("leave_game");
network::send_data(cfg,sock);
std::cerr << "attempt to join unknown game\n";
return;
}
if(it->player_is_banned(sock)) {
network::send_data(construct_error("You are banned from this game"),sock);
return;
}
lobby_players_.remove_player(sock);
//send them the game data
network::send_data(it->level(),sock);
it->add_player(sock);
//mark the player as unavailable in the lobby
const player_map::iterator pl = players_.find(sock);
if(pl != players_.end()) {
pl->second.mark_available(false);
lobby_players_.send_data(sync_initial_response());
} else {
std::cerr << "ERROR: Could not find player in map\n";
}
}
//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) {
const player_map::const_iterator p = players_.find(sock);
wassert(p != players_.end());
(*message)["sender"] = p->second.name();
truncate_message((*message)["message"]);
lobby_players_.send_data(data,sock);
}
}
void server::process_data_from_player_in_game(const network::connection sock, config& data, config& gamelist)
{
std::vector<game>::iterator g;
for(g = games_.begin(); g != games_.end(); ++g) {
if(g->is_member(sock))
break;
}
if(g == games_.end()) {
std::cerr << "ERROR: unknown socket " << games_.size() << "\n";
return;
}
//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(g->is_owner(sock) && data.child("change_controller") != NULL) {
const config& change = *data.child("change_controller");
const std::string& result = g->transfer_side_control(change);
if(result == "") {
const config& msg = construct_server_message(change["player"] + " takes control of side " + change["side"],*g);
g->send_data(msg);
} else {
const config& msg = construct_server_message(result,*g);
network::send_data(msg,sock);
}
return;
}
//if the owner is banning someone from the game
if(g->is_owner(sock) && data.child("ban") != NULL) {
const config& ban = *data.child("ban");
const std::string& name = ban["username"];
player_map::iterator pl;
for(pl = players_.begin(); pl != players_.end(); ++pl) {
if(pl->second.name() == name) {
break;
}
}
if(pl->first != sock && pl != players_.end()) {
g->ban_player(pl->first);
const config& msg = construct_server_message("You have been banned",*g);
network::send_data(msg,pl->first);
config leave_game;
leave_game.add_child("leave_game");
network::send_data(leave_game,pl->first);
g->describe_slots();
lobby_players_.add_player(pl->first);
//mark the player as available in the lobby
pl->second.mark_available(true);
//send the player who was banned the lobby game list
network::send_data(initial_response_,pl->first);
//send all other players in the lobby the update to the lobby
lobby_players_.send_data(sync_initial_response(),sock);
} else if(pl == players_.end()) {
const config& response = construct_server_message(
"Ban failed: user '" + name + "' not found",*g);
network::send_data(response,sock);
}
} else if(data.child("ban")) {
const config& response = construct_server_message(
"You cannot ban: not the game creator",*g);
network::send_data(response,sock);
}
//if this is data describing changes to a game.
else if(g->is_owner(sock) && data.child("scenario_diff")) {
g->level().apply_diff(*data.child("scenario_diff"));
g->send_data(data,sock);
const bool lobby_changes = g->describe_slots();
if(lobby_changes) {
lobby_players_.send_data(sync_initial_response());
}
}
//if this is data describing the level for a game.
else if(g->is_owner(sock) && data.child("side") != NULL) {
const bool is_init = g->level_init();
//if this game is having its level data initialized
//for the first time, and is ready for players to join.
//We should currently have a summary of the game in g->level().
//we want to move this summary to the initial_response_, 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"];
}
//update our config object which describes the
//open games, and notifies the game of where its description
//is located at
config& desc = gamelist.add_child("game",g->level());
g->set_description(&desc);
//record the full description of the scenario in g->level()
g->level() = data;
g->describe_slots();
//send all players in the lobby the update to the list of games
lobby_players_.send_data(sync_initial_response());
} else {
//we've already initialized this scenario, but clobber its old
//contents with the new ones given here
g->level() = data;
}
//send all players in the level, except the sender, the new data
g->send_data(data,sock);
return;
}
const string_map::const_iterator side = data.values.find("side");
if(side != data.values.end()) {
const bool res = g->take_side(sock,data);
config response;
if(res) {
std::cerr << "played joined side\n";
response["side_secured"] = side->second;
//update the number of available slots
const bool res = g->describe_slots();
if(res) {
lobby_players_.send_data(sync_initial_response());
}
} else {
response["failed"] = "yes";
}
network::send_data(response,sock);
return;
}
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);
g->start_game();
lobby_players_.send_data(sync_initial_response());
return;
} else if(data.child("leave_game")) {
const bool needed = g->is_needed(sock);
if(needed) {
//tell all other players the game is over,
//because a needed player has left
config cfg;
cfg.add_child("leave_game");
g->send_data(cfg);
//delete the game's description
config* const gamelist = initial_response_.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(sync_initial_response());
//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(true);
}
}
//put the players back in the lobby and send
//them the game list and user list again
g->send_data(initial_response_);
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(sync_initial_response());
} else {
g->remove_player(sock);
g->describe_slots();
lobby_players_.add_player(sock);
//mark the player as available in the lobby
const player_map::iterator pl = players_.find(sock);
if(pl != players_.end()) {
pl->second.mark_available(true);
} else {
std::cerr << "ERROR: Could not find player in map\n";
}
//send the player who has quit the game list
network::send_data(initial_response_,sock);
//send all other players in the lobby the update to the lobby
lobby_players_.send_data(sync_initial_response(),sock);
}
return;
} else if(data["side_secured"].empty() == false) {
return;
} else if(data["failed"].empty() == false) {
std::cerr << "ERROR: failure to get side\n";
return;
}
config* const turn = data.child("turn");
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
const bool res = g->process_commands(*turn);
if(res) {
lobby_players_.send_data(sync_initial_response());
}
//any private 'speak' commands must be repackaged seperate
//to other commands, and re-sent, since they should only go
//to some clients.
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) {
config* const speak = (*i)->child("speak");
if(speak == NULL) {
++nother;
continue;
}
truncate_message((*speak)["message"]);
//force the description to be correct to prevent
//spoofing of messages
const player_map::const_iterator pl = players_.find(sock);
wassert(pl != players_.end());
(*speak)["description"] = pl->second.name();
if((*speak)["team_name"] == "") {
++npublic;
} else {
++nprivate;
team_name = (*speak)["team_name"];
}
}
//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) {
g->send_data_team(data,team_name,sock);
return;
}
//at the moment, if private messages are mixed in with other
//data, then let them go through. It's exceedingly unlikely that
//this will happen anyway, and if it does, the client should
//respect not displaying the message.
}
//forward data to all players who are in the game,
//except for the original data sender
g->send_data(data,sock);
if(g->started()) {
g->record_data(data);
}
}
void server::delete_game(std::vector<game>::iterator i)
{
metrics_.game_terminated(i->termination_reason());