made it so when player quits multiplayer game, ...

...game creator chooses to replace them with ai/local player or abort.

fixed problem when player joins game and then leaves
This commit is contained in:
uid68803 2004-01-07 17:53:40 +00:00
parent 2eb5a56a1f
commit fe9d34d6db
12 changed files with 279 additions and 178 deletions

View file

@ -241,7 +241,7 @@ awaiting_connections="Waiting for players to connect"
position_taken="Filled"
multiplayer_save_name="multiplayer save"
position_vacant="Available"
observer="Observer"
observer="&misc/observer.png,Observer"
generic_player="player"
host_game="Host Multiplayer Game"
@ -270,6 +270,10 @@ load_game_heading="Load Game"
load_game_message="Choose the game to load"
replay_game_message="Show replay of game up to save point?"
show_replay="Show replay"
player_leave_message="has left the game. What do you want to do?"
replace_ai_message="Replace with AI"
replace_local_message="Replace with local player"
abort_game_message="Abort game"
save_game_error="A network disconnection has occured, and the game cannot continue. Do you want to save the game?"
network_sync_error="The games are out of sync and will have to exit. Do you want to save an error log of your game?"
multiplayer_save_name="multiplayer save"

View file

@ -39,9 +39,11 @@
network_game_manager::~network_game_manager()
{
if(network::nconnections() > 0) {
std::cerr << "sending leave_game\n";
config cfg;
cfg.add_child("leave_game");
network::send_data(cfg);
std::cerr << "sent leave_game\n";
}
}

View file

@ -278,7 +278,7 @@ void play_multiplayer_client(display& disp, game_data& units_data, config& cfg,
if((**s)["controller"] == "network" &&
(**s)["taken"] != "yes") {
choice_map[choices.size()] = 1 + s - sides_list.begin();
choices.push_back((**s)["name"] + " - " + (**s)["type"]);
choices.push_back((**s)["name"] + "," + (**s)["type"]);
}
race_names.push_back((**s)["name"]);

View file

@ -627,110 +627,112 @@ void mp_connect::update_network()
std::cerr << "Received connection\n";
network::send_data(*level_,sock);
}
}
}
config cfg;
const config::child_list& sides = level_->get_children("side");
config cfg;
const config::child_list& sides = level_->get_children("side");
try {
sock = network::receive_data(cfg);
} catch(network::error& e) {
std::cerr << "caught networking error. we are " << (network::is_server() ? "" : "NOT") << " a server\n";
sock = 0;
network::connection sock;
//if the problem isn't related to any specific connection,
//it's a general error and we should just re-throw the error
//likewise if we are not a server, we cannot afford any connection
//to go down, so also re-throw the error
if(!e.socket || !network::is_server()) {
e.disconnect();
throw network::error(e.message);
}
try {
sock = network::receive_data(cfg);
} catch(network::error& e) {
std::cerr << "caught networking error. we are " << (network::is_server() ? "" : "NOT") << " a server\n";
sock = 0;
bool changes = false;
//if the problem isn't related to any specific connection,
//it's a general error and we should just re-throw the error
//likewise if we are not a server, we cannot afford any connection
//to go down, so also re-throw the error
if(!e.socket || !network::is_server()) {
e.disconnect();
throw network::error(e.message);
}
//a socket has disconnected. Remove its positions.
for(std::map<config*,network::connection>::iterator i = positions_.begin();
i != positions_.end(); ++i) {
if(i->second == e.socket) {
changes = true;
i->second = 0;
i->first->values.erase("taken");
bool changes = false;
// Add to combo list
std::stringstream str;
str << i->first->values["description"];
//remove_player(str.str());
i->first->values["description"]="";
}
}
//a socket has disconnected. Remove its positions.
for(std::map<config*,network::connection>::iterator i = positions_.begin();
i != positions_.end(); ++i) {
if(i->second == e.socket) {
changes = true;
i->second = 0;
i->first->values.erase("taken");
//now disconnect the socket
e.disconnect();
// Add to combo list
std::stringstream str;
str << i->first->values["description"];
//remove_player(str.str());
i->first->values["description"]="";
}
}
//if there have been changes to the positions taken,
//then notify other players
if(changes) {
//now disconnect the socket
e.disconnect();
//if there have been changes to the positions taken,
//then notify other players
if(changes) {
network::send_data(*level_);
}
}
//No network errors
if(sock) {
const int side_drop = atoi(cfg["side_drop"].c_str())-1;
if(side_drop >= 0 && side_drop < int(sides.size())) {
std::map<config*,network::connection>::iterator pos = positions_.find(sides[side_drop]);
if(pos != positions_.end()) {
pos->second = 0;
pos->first->values.erase("taken");
pos->first->values["description"] = "";
network::send_data(*level_);
}
}
const int side_taken = atoi(cfg["side"].c_str())-1;
if(side_taken >= 0 && side_taken < int(sides.size())) {
std::map<config*,network::connection>::iterator pos = positions_.find(sides[side_taken]);
if(pos != positions_.end()) {
if(!pos->second) {
std::cerr << "client has taken a valid position\n";
//broadcast to everyone the new game status
pos->first->values["taken"] = "yes";
pos->first->values["description"] = cfg["description"];
pos->first->values["name"] = cfg["name"];
pos->first->values["type"] = cfg["type"];
pos->first->values["recruit"] = cfg["recruit"];
pos->first->values["music"] = cfg["music"];
positions_[sides[side_taken]] = sock;
network::send_data(*level_);
}
}
//No network errors
if(sock) {
const int side_drop = atoi(cfg["side_drop"].c_str())-1;
if(side_drop >= 0 && side_drop < int(sides.size())) {
std::map<config*,network::connection>::iterator pos = positions_.find(sides[side_drop]);
if(pos != positions_.end()) {
pos->second = 0;
pos->first->values.erase("taken");
pos->first->values["description"] = "";
network::send_data(*level_);
}
}
std::cerr << "sent player data\n";
const int side_taken = atoi(cfg["side"].c_str())-1;
if(side_taken >= 0 && side_taken < int(sides.size())) {
std::map<config*,network::connection>::iterator pos = positions_.find(sides[side_taken]);
if(pos != positions_.end()) {
if(!pos->second) {
std::cerr << "client has taken a valid position\n";
//send a reply telling the client they have secured
//the side they asked for
std::stringstream side;
side << (side_taken+1);
config reply;
reply.values["side_secured"] = side.str();
std::cerr << "going to send data...\n";
network::send_data(reply,sock);
//broadcast to everyone the new game status
pos->first->values["taken"] = "yes";
pos->first->values["description"] = cfg["description"];
pos->first->values["name"] = cfg["name"];
pos->first->values["type"] = cfg["type"];
pos->first->values["recruit"] = cfg["recruit"];
pos->first->values["music"] = cfg["music"];
positions_[sides[side_taken]] = sock;
network::send_data(*level_);
std::cerr << "sent player data\n";
//send a reply telling the client they have secured
//the side they asked for
std::stringstream side;
side << (side_taken+1);
config reply;
reply.values["side_secured"] = side.str();
std::cerr << "going to send data...\n";
network::send_data(reply,sock);
// Add to combo list
std::stringstream str;
str << cfg["description"];
add_player(str.str());
} else {
config response;
response.values["failed"] = "yes";
network::send_data(response,sock);
}
} else {
std::cerr << "tried to take illegal side: " << side_taken << "\n";
}
// Add to combo list
std::stringstream str;
str << cfg["description"];
add_player(str.str());
} else {
std::cerr << "tried to take unknown side: " << side_taken << "\n";
config response;
response.values["failed"] = "yes";
network::send_data(response,sock);
}
} else {
std::cerr << "tried to take illegal side: " << side_taken << "\n";
}
} else {
std::cerr << "tried to take unknown side: " << side_taken << "\n";
}
}
}

View file

@ -7,6 +7,7 @@
#include <algorithm>
#include <cassert>
#include <queue>
#include <set>
#include <vector>
namespace {
@ -28,11 +29,25 @@ partial_map::const_iterator current_connection = received_data.end();
TCPsocket server_socket;
std::queue<network::connection> disconnection_queue;
std::set<network::connection> bad_sockets;
}
namespace network {
error::error(const std::string& msg, connection sock) : message(msg), socket(sock)
{
bad_sockets.insert(socket);
}
void error::disconnect()
{
bad_sockets.erase(socket);
if(socket) {
network::disconnect(socket);
}
}
manager::manager() : free_(true)
{
//if the network is already being managed
@ -178,6 +193,9 @@ connection receive_data(config& cfg, connection connection_num, int timeout)
throw error("",sock);
}
if(bad_sockets.count(connection_num) || bad_sockets.count(0))
return 0;
if(sockets.empty()) {
return 0;
}
@ -196,11 +214,8 @@ connection receive_data(config& cfg, connection connection_num, int timeout)
char num_buf[4];
size_t len = SDLNet_TCP_Recv(*i,num_buf,4);
if(len == 0) {
if(len != 4) {
throw error("Remote host disconnected",*i);
} else if(len != 4) {
std::cerr << "received bad packet length: " << len << "/4\n";
throw error(std::string("network error receiving length data: ") + SDLNet_GetError(),*i);
}
len = SDLNet_Read32(num_buf);
@ -256,6 +271,9 @@ connection receive_data(config& cfg, connection connection_num, int timeout)
void send_data(const config& cfg, connection connection_num)
{
if(bad_sockets.count(connection_num) || bad_sockets.count(0))
return;
log_scope("sending data");
if(!connection_num) {
std::cerr << "sockets: " << sockets.size() << "\n";
@ -285,7 +303,7 @@ void send_data(const config& cfg, connection connection_num)
value.size()+1);
if(res < int(value.size()+1)) {
std::cerr << "sending data failed: " << res << "/" << value.size() << ": " << SDL_GetError() << "\n";
std::cerr << "sending data failed: " << res << "/" << value.size() << "\n";
throw error("Could not send data over socket",connection_num);
}
}

View file

@ -82,12 +82,11 @@ std::pair<int,int> current_transfer_stats();
struct error
{
error(const std::string& msg, connection sock=0)
: message(msg), socket(sock) {}
error(const std::string& msg, connection sock=0);
std::string message;
connection socket;
void disconnect() { if(socket) { network::disconnect(socket); } }
void disconnect();
};
}

View file

@ -312,6 +312,9 @@ LEVEL_RESULT play_level(game_data& gameinfo, config& game_config,
sound::play_music(team_it->music());
}
//goto this label if the type of a team (human/ai/networked) has changed mid-turn
redo_turn:
if(!replaying && team_it->is_human()) {
std::cerr << "is human...\n";
@ -366,10 +369,48 @@ LEVEL_RESULT play_level(game_data& gameinfo, config& game_config,
for(;;) {
network::connection res = network::receive_data(cfg);
if(res && cfg.child("leave_game")) {
if(res && cfg.child("leave_game") != NULL) {
throw network::error("");
}
//if a side has dropped out of the game.
if(res && cfg["side_drop"] != "") {
const size_t side = atoi(cfg["side_drop"].c_str())-1;
if(side >= teams.size()) {
std::cerr << "unknown side " << side << " is dropping game\n";
throw network::error("");
}
int action = 0;
//see if the side still has a leader alive. If they have
//no leader, we assume they just want to be replaced by
//the AI.
const unit_map::const_iterator leader = find_leader(units,side+1);
if(leader != units.end()) {
std::vector<std::string> options;
options.push_back(string_table["replace_ai_message"]);
options.push_back(string_table["replace_local_message"]);
options.push_back(string_table["abort_game_message"]);
const std::string msg = leader->second.description() + " " + string_table["player_leave_message"];
action = gui::show_dialog(gui,NULL,"",msg,gui::OK_ONLY,&options);
}
//make the player an AI, and redo this turn, in case
//it was the current player's team who has just changed into
//an AI.
if(action == 0) {
teams[side].make_ai();
goto redo_turn;
} else if(action == 1) {
teams[side].make_human();
goto redo_turn;
} else {
throw network::error("");
}
}
if(res && cfg.child("turn") != NULL) {
//forward the data to other peers
network::send_data_all_except(cfg,res);

View file

@ -16,7 +16,7 @@ bool game::is_member(network::connection player) const
bool game::is_needed(network::connection player) const
{
assert(!players_.empty());
return player == players_.front() || started_ && sides_.count(player);
return player == players_.front();
}
void game::start_game()
@ -63,8 +63,7 @@ void game::remove_player(network::connection player)
if(itor != players_.end())
players_.erase(itor);
std::map<network::connection,std::string>::iterator side
= sides_.find(player);
std::map<network::connection,std::string>::iterator side = sides_.find(player);
if(side != sides_.end()) {
//send the host a notification of removal of this side
if(players_.empty() == false) {

View file

@ -22,31 +22,45 @@ config construct_error(const std::string& msg)
return cfg;
}
void run_server()
class server
{
const network::manager net_manager;
const network::server_manager server;
public:
server();
void run();
private:
void delete_game(std::vector<game>::iterator i);
config login_response;
login_response.add_child("mustlogin");
login_response["version"] = game_config::version;
const network::manager net_manager_;
const network::server_manager server_;
config initial_response;
config& gamelist = initial_response.add_child("gamelist");
config login_response_;
config initial_response_;
typedef std::map<network::connection,player> player_map;
player_map players;
player_map players_;
game not_logged_in;
game lobby_players;
std::vector<game> games;
game not_logged_in_;
game lobby_players_;
std::vector<game> games_;
};
server::server() : net_manager_(), server_()
{
login_response_.add_child("mustlogin");
login_response_["version"] = game_config::version;
}
void server::run()
{
config& gamelist = initial_response_.add_child("gamelist");
for(;;) {
try {
network::connection sock = network::accept_connection();
if(sock) {
network::send_data(login_response,sock);
not_logged_in.add_player(sock);
network::send_data(login_response_,sock);
not_logged_in_.add_player(sock);
}
config data;
@ -57,7 +71,7 @@ void run_server()
//if someone who is not yet logged in is sending
//login details
if(not_logged_in.is_member(sock)) {
if(not_logged_in_.is_member(sock)) {
const config* const login = data.child("login");
//client must send a login first.
@ -78,33 +92,33 @@ void run_server()
//check the username isn't already taken
player_map::const_iterator p;
for(p = players.begin(); p != players.end(); ++p) {
for(p = players_.begin(); p != players_.end(); ++p) {
if(p->second.name() == username) {
break;
}
}
if(p != players.end()) {
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");
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));
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);
not_logged_in_.remove_player(sock);
lobby_players_.add_player(sock);
//currently update user list to all players who are in lobby
//(later may optimize to only send new login information
//to all but the new player)
lobby_players.send_data(initial_response);
lobby_players_.send_data(initial_response_);
} else if(lobby_players.is_member(sock)) {
} else if(lobby_players_.is_member(sock)) {
const config* const create_game = data.child("create_game");
if(create_game != NULL) {
@ -112,15 +126,15 @@ void run_server()
//create the new game, remove the player from the
//lobby and put him/her in the game they have created
games.push_back(game());
lobby_players.remove_player(sock);
games.back().add_player(sock);
games_.push_back(game());
lobby_players_.remove_player(sock);
games_.back().add_player(sock);
//store the game data here at the moment
games.back().level() = *create_game;
games_.back().level() = *create_game;
std::stringstream converter;
converter << games.back().id();
games.back().level()["id"] = converter.str();
converter << games_.back().id();
games_.back().level()["id"] = converter.str();
continue;
}
@ -131,14 +145,14 @@ void run_server()
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(),
std::find_if(games_.begin(),games_.end(),
game_id_matches(nid));
if(it == games.end()) {
if(it == games_.end()) {
std::cerr << "attempt to join unknown game\n";
continue;
}
lobby_players.remove_player(sock);
lobby_players_.remove_player(sock);
//send them the game data
network::send_data(it->level(),sock);
@ -150,21 +164,20 @@ void run_server()
//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);
assert(p != players.end());
const player_map::const_iterator p = players_.find(sock);
assert(p != players_.end());
(*message)["sender"] = p->second.name();
lobby_players.send_data(data);
lobby_players_.send_data(data);
}
} else {
std::vector<game>::iterator g;
for(g = games.begin(); g != games.end(); ++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";
if(g == games_.end()) {
std::cerr << "ERROR: unknown socket " << games_.size() << "\n";
continue;
}
@ -186,7 +199,7 @@ void run_server()
g->set_description(&desc);
//send all players in the lobby the list of games
lobby_players.send_data(initial_response);
lobby_players_.send_data(initial_response_);
}
//record the new level data, and send to all players
@ -218,7 +231,7 @@ void run_server()
} else if(data.child("leave_game")) {
const bool needed = g->is_needed(sock);
g->remove_player(sock);
lobby_players.add_player(sock);
lobby_players_.add_player(sock);
if(needed) {
@ -229,7 +242,7 @@ void run_server()
g->send_data(cfg);
//delete the game's description
config* const gamelist = initial_response.child("gamelist");
config* const gamelist = initial_response_.child("gamelist");
assert(gamelist != NULL);
const config::child_itors vg = gamelist->child_range("game");
@ -241,13 +254,13 @@ void run_server()
//put the players back in the lobby and send
//them the game list and user list again
g->send_data(initial_response);
lobby_players.add_players(*g);
games.erase(g);
g->send_data(initial_response_);
lobby_players_.add_players(*g);
games_.erase(g);
}
//send the player who has quit the new game list
network::send_data(initial_response,sock);
network::send_data(initial_response_,sock);
continue;
} else if(data["side_secured"].empty() == false) {
@ -271,37 +284,21 @@ void run_server()
} else {
std::cerr << "socket closed: " << e.message << "\n";
const std::map<network::connection,player>::iterator pl_it =
players.find(e.socket);
if(pl_it != players.end()) {
const config::child_list& users = initial_response.get_children("user");
const std::map<network::connection,player>::iterator pl_it = players_.find(e.socket);
if(pl_it != players_.end()) {
const config::child_list& users = initial_response_.get_children("user");
const size_t index = std::find(users.begin(),users.end(),pl_it->second.config_address()) - users.begin();
if(index < users.size())
initial_response.remove_child("user",index);
initial_response_.remove_child("user",index);
players.erase(pl_it);
players_.erase(pl_it);
}
not_logged_in.remove_player(e.socket);
lobby_players.remove_player(e.socket);
for(std::vector<game>::iterator i = games.begin();
i != games.end(); ++i) {
not_logged_in_.remove_player(e.socket);
lobby_players_.remove_player(e.socket);
for(std::vector<game>::iterator i = games_.begin(); i != games_.end(); ++i) {
if(i->is_needed(e.socket)) {
//delete the game's configuration
config* const gamelist =
initial_response.child("gamelist");
assert(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;
delete *g;
gamelist->remove_child("game",index);
}
i->disconnect();
games.erase(i);
delete_game(i);
e.socket = 0;
break;
} else {
@ -315,7 +312,7 @@ void run_server()
//send all players the information that a player has logged
//out of the system
lobby_players.send_data(initial_response);
lobby_players_.send_data(initial_response_);
}
continue;
@ -328,10 +325,27 @@ void run_server()
}
}
void server::delete_game(std::vector<game>::iterator i)
{
//delete the game's configuration
config* const gamelist = initial_response_.child("gamelist");
assert(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;
delete *g;
gamelist->remove_child("game",index);
}
i->disconnect();
games_.erase(i);
}
int main(int argc, char** argv)
{
try {
run_server();
server().run();
} catch(network::error& e) {
std::cerr << "error starting server: " << e.message << "\n";
return -1;

View file

@ -376,6 +376,21 @@ bool team::is_network() const
return info_.controller == team_info::NETWORK;
}
void team::make_human()
{
info_.controller = team_info::HUMAN;
}
void team::make_ai()
{
info_.controller = team_info::AI;
}
void team::make_network()
{
info_.controller = team_info::NETWORK;
}
double team::leader_value() const
{
return info_.leader_value;

View file

@ -90,6 +90,10 @@ public:
bool is_network() const;
bool is_ai() const;
void make_human();
void make_network();
void make_ai();
double leader_value() const;
double village_value() const;

View file

@ -331,8 +331,9 @@ void menu::draw_item(int item)
if(buffer_.get() != NULL) {
const int ypos = items_start()+(item-first_item_on_screen_)*rect.h;
SDL_Rect srcrect = {0,ypos,rect.w,rect.h};
SDL_Rect dstrect = rect;
SDL_BlitSurface(buffer_,&srcrect,
display_->video().getSurface(),&rect);
display_->video().getSurface(),&dstrect);
}
gui::draw_solid_tinted_rectangle(x_,rect.y,width(),rect.h,
@ -340,7 +341,7 @@ void menu::draw_item(int item)
item == selected_ ? 0.6 : 0.2,
display_->video().getSurface());
SDL_Rect area = {0,0,display_->x(),display_->y()};
SDL_Rect area = display_->screen_area();
const std::vector<int>& widths = column_widths();
@ -420,6 +421,8 @@ SDL_Rect menu::get_item_rect(int item) const
if(x_ > 0 && y_ > 0)
itemRects_.insert(std::pair<int,SDL_Rect>(item,res));
std::cerr << "item " << item << " is " << res.w << "," << res.h << "\n";
return res;
}