diff options
Diffstat (limited to 'src/net_server.c')
-rw-r--r-- | src/net_server.c | 575 |
1 files changed, 386 insertions, 189 deletions
diff --git a/src/net_server.c b/src/net_server.c index 189e824f..a2868370 100644 --- a/src/net_server.c +++ b/src/net_server.c @@ -21,14 +21,15 @@ // Network server code // +#include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include "config.h" -#include "doomdef.h" -#include "doomstat.h" +#include "doomtype.h" +#include "d_mode.h" #include "i_system.h" #include "i_timer.h" @@ -55,7 +56,13 @@ typedef enum { - // waiting for the game to start + // waiting for the game to be "launched" (key player to press the start + // button) + + SERVER_WAITING_LAUNCH, + + // game has been launched, we are waiting for all players to be ready + // so the game can start. SERVER_WAITING_START, @@ -64,7 +71,7 @@ typedef enum SERVER_IN_GAME, } net_server_state_t; -typedef struct +typedef struct { boolean active; int player_number; @@ -73,13 +80,18 @@ typedef struct int last_send_time; char *name; + // If true, the client has sent the NET_PACKET_TYPE_GAMESTART + // message indicating that it is ready for the game to start. + + boolean ready; + // Time that this client connected to the server. // This is used to determine the controller (oldest client). unsigned int connect_time; // Last time new gamedata was received from this client - + int last_gamedata_time; // recording a demo without -longtics @@ -96,6 +108,10 @@ typedef struct unsigned int acknowledged; + // Value of max_players specified by the client on connect. + + int max_players; + // Observer: receives data but does not participate in the game. boolean drone; @@ -109,6 +125,10 @@ typedef struct unsigned int is_freedoom; + // Player class (for Hexen) + + int player_class; + } net_client_t; // structure used for the recv window @@ -135,7 +155,7 @@ typedef struct static net_server_state_t server_state; static boolean server_initialized = false; static net_client_t clients[MAXNETNODES]; -static net_client_t *sv_players[MAXPLAYERS]; +static net_client_t *sv_players[NET_MAXPLAYERS]; static net_context_t *server_context; static unsigned int sv_gamemode; static unsigned int sv_gamemission; @@ -150,7 +170,7 @@ static unsigned int master_resolve_time; // receive window static unsigned int recvwindow_start; -static net_client_recv_t recvwindow[BACKUPTICS][MAXPLAYERS]; +static net_client_recv_t recvwindow[BACKUPTICS][NET_MAXPLAYERS]; #define NET_SV_ExpandTicNum(b) NET_ExpandTicNum(recvwindow_start, (b)) @@ -239,7 +259,7 @@ static void NET_SV_AssignPlayers(void) } } - for (; pl<MAXPLAYERS; ++pl) + for (; pl<NET_MAXPLAYERS; ++pl) { sv_players[pl] = NULL; } @@ -254,7 +274,7 @@ static int NET_SV_NumPlayers(void) result = 0; - for (i=0; i<MAXPLAYERS; ++i) + for (i=0; i<NET_MAXPLAYERS; ++i) { if (sv_players[i] != NULL && ClientConnected(sv_players[i])) { @@ -265,6 +285,42 @@ static int NET_SV_NumPlayers(void) return result; } +// Returns the number of players ready to start the game. + +static int NET_SV_NumReadyPlayers(void) +{ + int result = 0; + int i; + + for (i = 0; i < MAXNETNODES; ++i) + { + if (ClientConnected(&clients[i]) + && !clients[i].drone && clients[i].ready) + { + ++result; + } + } + + return result; +} + +// Returns the maximum number of players that can play. + +static int NET_SV_MaxPlayers(void) +{ + int i; + + for (i = 0; i < MAXNETNODES; ++i) + { + if (ClientConnected(&clients[i])) + { + return clients[i].max_players; + } + } + + return NET_MAXPLAYERS; +} + // Returns the number of drones currently connected. static int NET_SV_NumDrones(void) @@ -305,6 +361,95 @@ static int NET_SV_NumClients(void) return count; } +// returns a pointer to the client which controls the server + +static net_client_t *NET_SV_Controller(void) +{ + net_client_t *best; + int i; + + // Find the oldest client (first to connect). + + best = NULL; + + for (i=0; i<MAXNETNODES; ++i) + { + // Can't be controller? + + if (!ClientConnected(&clients[i]) || clients[i].drone) + { + continue; + } + + if (best == NULL || clients[i].connect_time < best->connect_time) + { + best = &clients[i]; + } + } + + return best; +} + +static void NET_SV_SendWaitingData(net_client_t *client) +{ + net_waitdata_t wait_data; + net_packet_t *packet; + net_client_t *controller; + int i; + + NET_SV_AssignPlayers(); + + controller = NET_SV_Controller(); + + wait_data.num_players = NET_SV_NumPlayers(); + wait_data.num_drones = NET_SV_NumDrones(); + wait_data.ready_players = NET_SV_NumReadyPlayers(); + wait_data.max_players = NET_SV_MaxPlayers(); + wait_data.is_controller = (client == controller); + wait_data.consoleplayer = client->player_number; + + // Send the WAD and dehacked checksums of the controlling client. + // If no controller found (?), send the details that the client + // is expecting anyway. + + if (controller != NULL) + { + controller = client; + } + + memcpy(&wait_data.wad_sha1sum, &controller->wad_sha1sum, + sizeof(sha1_digest_t)); + memcpy(&wait_data.deh_sha1sum, &controller->deh_sha1sum, + sizeof(sha1_digest_t)); + wait_data.is_freedoom = controller->is_freedoom; + + // set name and address of each player: + + for (i = 0; i < wait_data.num_players; ++i) + { + strncpy(wait_data.player_names[i], + sv_players[i]->name, + MAXPLAYERNAME); + wait_data.player_names[i][MAXPLAYERNAME-1] = '\0'; + + strncpy(wait_data.player_addrs[i], + NET_AddrToString(sv_players[i]->addr), + MAXPLAYERNAME); + wait_data.player_addrs[i][MAXPLAYERNAME-1] = '\0'; + } + + // Construct packet: + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_PACKET_TYPE_WAITING_DATA); + NET_WriteWaitData(packet, &wait_data); + + // Send packet to client and free + + NET_Conn_SendPacket(&client->connection, packet); + NET_FreePacket(packet); +} + // Find the latest tic which has been acknowledged as received by // all clients. @@ -354,7 +499,7 @@ static void NET_SV_AdvanceWindow(void) should_advance = true; - for (i=0; i<MAXPLAYERS; ++i) + for (i=0; i<NET_MAXPLAYERS; ++i) { if (sv_players[i] == NULL || !ClientConnected(sv_players[i])) { @@ -387,35 +532,6 @@ static void NET_SV_AdvanceWindow(void) } } -// returns a pointer to the client which controls the server - -static net_client_t *NET_SV_Controller(void) -{ - net_client_t *best; - int i; - - // Find the oldest client (first to connect). - - best = NULL; - - for (i=0; i<MAXNETNODES; ++i) - { - // Can't be controller? - - if (!ClientConnected(&clients[i]) || clients[i].drone) - { - continue; - } - - if (best == NULL || clients[i].connect_time < best->connect_time) - { - best = &clients[i]; - } - } - - return best; -} - // Given an address, find the corresponding client static net_client_t *NET_SV_FindClient(net_addr_t *addr) @@ -464,6 +580,7 @@ static void NET_SV_InitNewClient(net_client_t *client, client->sendseq = 0; client->acknowledged = 0; client->drone = false; + client->ready = false; client->last_gamedata_time = 0; @@ -477,11 +594,7 @@ static void NET_SV_ParseSYN(net_packet_t *packet, net_addr_t *addr) { unsigned int magic; - unsigned int cl_gamemode, cl_gamemission; - unsigned int cl_recording_lowres; - unsigned int cl_drone; - unsigned int is_freedoom; - sha1_digest_t deh_sha1sum, wad_sha1sum; + net_connect_data_t data; char *player_name; char *client_version; int i; @@ -520,29 +633,30 @@ static void NET_SV_ParseSYN(net_packet_t *packet, // will simply not function at all. // - if (M_CheckParm("-ignoreversion") == 0) + if (M_CheckParm("-ignoreversion") == 0) { NET_SV_SendReject(addr, - "Version mismatch: server version is: " - PACKAGE_STRING); + "Different " PACKAGE_NAME " versions cannot play a net game!\n" + "Version mismatch: server version is: " PACKAGE_STRING); return; } } // read the game mode and mission - if (!NET_ReadInt16(packet, &cl_gamemode) - || !NET_ReadInt16(packet, &cl_gamemission) - || !NET_ReadInt8(packet, &cl_recording_lowres) - || !NET_ReadInt8(packet, &cl_drone) - || !NET_ReadSHA1Sum(packet, wad_sha1sum) - || !NET_ReadSHA1Sum(packet, deh_sha1sum) - || !NET_ReadInt8(packet, &is_freedoom)) + if (!NET_ReadConnectData(packet, &data)) { return; } - if (!NET_ValidGameMode(cl_gamemode, cl_gamemission)) + if (!D_ValidGameMode(data.gamemission, data.gamemode)) + { + return; + } + + // Check max_players value. This must be in a sensible range. + + if (data.max_players > NET_MAXPLAYERS) { return; } @@ -555,17 +669,17 @@ static void NET_SV_ParseSYN(net_packet_t *packet, { return; } - + // received a valid SYN // not accepting new connections? - - if (server_state != SERVER_WAITING_START) + + if (server_state != SERVER_WAITING_LAUNCH) { NET_SV_SendReject(addr, "Server is not currently accepting connections"); return; } - + // allocate a client slot if there isn't one already if (client == NULL) @@ -609,7 +723,7 @@ static void NET_SV_ParseSYN(net_packet_t *packet, NET_SV_AssignPlayers(); num_players = NET_SV_NumPlayers(); - if ((!cl_drone && num_players >= MAXPLAYERS) + if ((!data.drone && num_players >= NET_SV_MaxPlayers()) || NET_SV_NumClients() >= MAXNETNODES) { NET_SV_SendReject(addr, "Server is full!"); @@ -622,33 +736,35 @@ static void NET_SV_ParseSYN(net_packet_t *packet, // Adopt the game mode and mission of the first connecting client - if (num_players == 0 && !cl_drone) + if (num_players == 0 && !data.drone) { - sv_gamemode = cl_gamemode; - sv_gamemission = cl_gamemission; + sv_gamemode = data.gamemode; + sv_gamemission = data.gamemission; } // Save the SHA1 checksums - memcpy(client->wad_sha1sum, wad_sha1sum, sizeof(sha1_digest_t)); - memcpy(client->deh_sha1sum, deh_sha1sum, sizeof(sha1_digest_t)); - client->is_freedoom = is_freedoom; + memcpy(client->wad_sha1sum, data.wad_sha1sum, sizeof(sha1_digest_t)); + memcpy(client->deh_sha1sum, data.deh_sha1sum, sizeof(sha1_digest_t)); + client->is_freedoom = data.is_freedoom; + client->max_players = data.max_players; // Check the connecting client is playing the same game as all // the other clients - if (cl_gamemode != sv_gamemode || cl_gamemission != sv_gamemission) + if (data.gamemode != sv_gamemode || data.gamemission != sv_gamemission) { NET_SV_SendReject(addr, "You are playing the wrong game!"); return; } - + // Activate, initialize connection NET_SV_InitNewClient(client, addr, player_name); - client->recording_lowres = cl_recording_lowres; - client->drone = cl_drone; + client->recording_lowres = data.lowres_turn; + client->drone = data.drone; + client->player_class = data.player_class; } if (client->connection.state == NET_CONN_STATE_WAITING_ACK) @@ -658,56 +774,88 @@ static void NET_SV_ParseSYN(net_packet_t *packet, } } -// Parse a game start packet +// Parse a launch packet. This is sent by the key player when the "start" +// button is pressed, and causes the startup process to continue. -static void NET_SV_ParseGameStart(net_packet_t *packet, net_client_t *client) +static void NET_SV_ParseLaunch(net_packet_t *packet, net_client_t *client) { - net_gamesettings_t settings; - net_packet_t *startpacket; - int nowtime; - int i; - - if (client != NET_SV_Controller()) - { - // Only the controller can start a new game + net_packet_t *launchpacket; + int num_players; + unsigned int i; - return; - } + // Only the controller can launch the game. - if (!NET_ReadSettings(packet, &settings)) + if (client != NET_SV_Controller()) { - // Malformed packet - return; } - // Check the game settings are valid + // Can only launch when we are in the waiting state. - if (!NET_ValidGameSettings(sv_gamemode, sv_gamemission, &settings)) + if (server_state != SERVER_WAITING_LAUNCH) { return; } - if (server_state != SERVER_WAITING_START) + // Forward launch on to all clients. + + NET_SV_AssignPlayers(); + num_players = NET_SV_NumPlayers(); + + for (i=0; i<MAXNETNODES; ++i) { - // Can only start a game if we are in the waiting start state. + if (!ClientConnected(&clients[i])) + continue; - return; + launchpacket = NET_Conn_NewReliable(&clients[i].connection, + NET_PACKET_TYPE_LAUNCH); + NET_WriteInt8(launchpacket, num_players); } + // Now in launch state. + + server_state = SERVER_WAITING_START; +} + +// Transition to the in-game state and send all players the start game +// message. Invoked once all players have indicated they are ready to +// start the game. + +static void StartGame(void) +{ + net_packet_t *startpacket; + unsigned int i; + int nowtime; + // Assign player numbers NET_SV_AssignPlayers(); // Check if anyone is recording a demo and set lowres_turn if so. - settings.lowres_turn = false; + sv_settings.lowres_turn = false; - for (i=0; i<MAXPLAYERS; ++i) + for (i = 0; i < NET_MAXPLAYERS; ++i) { if (sv_players[i] != NULL && sv_players[i]->recording_lowres) { - settings.lowres_turn = true; + sv_settings.lowres_turn = true; + } + } + + sv_settings.num_players = NET_SV_NumPlayers(); + + // Copy player classes: + + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + if (sv_players[i] != NULL) + { + sv_settings.player_classes[i] = sv_players[i]->player_class; + } + else + { + sv_settings.player_classes[i] = 0; } } @@ -715,7 +863,7 @@ static void NET_SV_ParseGameStart(net_packet_t *packet, net_client_t *client) // Send start packets to each connected node - for (i=0; i<MAXNETNODES; ++i) + for (i = 0; i < MAXNETNODES; ++i) { if (!ClientConnected(&clients[i])) continue; @@ -725,20 +873,105 @@ static void NET_SV_ParseGameStart(net_packet_t *packet, net_client_t *client) startpacket = NET_Conn_NewReliable(&clients[i].connection, NET_PACKET_TYPE_GAMESTART); - NET_WriteInt8(startpacket, NET_SV_NumPlayers()); - NET_WriteInt8(startpacket, clients[i].player_number); - NET_WriteSettings(startpacket, &settings); + sv_settings.consoleplayer = clients[i].player_number; + + NET_WriteSettings(startpacket, &sv_settings); } // Change server state server_state = SERVER_IN_GAME; - sv_settings = settings; memset(recvwindow, 0, sizeof(recvwindow)); recvwindow_start = 0; } +// Returns true when all nodes have indicated readiness to start the game. + +static boolean AllNodesReady(void) +{ + unsigned int i; + + for (i = 0; i < MAXNETNODES; ++i) + { + if (ClientConnected(&clients[i]) && !clients[i].ready) + { + return false; + } + } + + return true; +} + +// Check if the game should start, and if so, start it. + +static void CheckStartGame(void) +{ + if (AllNodesReady()) + { + StartGame(); + } +} + +// Send waiting data with current status to all nodes that are ready to +// start the game. + +static void SendAllWaitingData(void) +{ + unsigned int i; + + for (i = 0; i < MAXNETNODES; ++i) + { + if (ClientConnected(&clients[i]) && clients[i].ready) + { + NET_SV_SendWaitingData(&clients[i]); + } + } +} + +// Parse a game start packet + +static void NET_SV_ParseGameStart(net_packet_t *packet, net_client_t *client) +{ + net_gamesettings_t settings; + + // Can only start a game if we are in the waiting start state. + + if (server_state != SERVER_WAITING_START) + { + return; + } + + if (client == NET_SV_Controller()) + { + if (!NET_ReadSettings(packet, &settings)) + { + // Malformed packet + + return; + } + + // Check the game settings are valid + + if (!NET_ValidGameSettings(sv_gamemode, sv_gamemission, &settings)) + { + return; + } + + sv_settings = settings; + } + + client->ready = true; + + CheckStartGame(); + + // Update all ready clients with the current state (number of players + // ready, etc.). This is used by games that show startup progress + // (eg. Hexen's spinal loading) + + SendAllWaitingData(); +} + // Send a resend request to a client static void NET_SV_SendResendRequest(net_client_t *client, int start, int end) @@ -1113,7 +1346,7 @@ void NET_SV_SendQueryResponse(net_addr_t *addr) // Number of players/maximum players querydata.num_players = NET_SV_NumPlayers(); - querydata.max_players = MAXPLAYERS; + querydata.max_players = NET_SV_MaxPlayers(); // Game mode/mission @@ -1191,7 +1424,7 @@ static void NET_SV_Packet(net_packet_t *packet, net_addr_t *addr) // Packet was eaten by the common connection code } else - { + { //printf("SV: %s: %i\n", NET_AddrToString(addr), packet_type); switch (packet_type) @@ -1199,6 +1432,9 @@ static void NET_SV_Packet(net_packet_t *packet, net_addr_t *addr) case NET_PACKET_TYPE_GAMESTART: NET_SV_ParseGameStart(packet, client); break; + case NET_PACKET_TYPE_LAUNCH: + NET_SV_ParseLaunch(packet, client); + break; case NET_PACKET_TYPE_GAMEDATA: NET_SV_ParseGameData(packet, client); break; @@ -1225,82 +1461,11 @@ static void NET_SV_Packet(net_packet_t *packet, net_addr_t *addr) } -static void NET_SV_SendWaitingData(net_client_t *client) -{ - net_packet_t *packet; - net_client_t *controller; - int num_players; - int i; - - NET_SV_AssignPlayers(); - - controller = NET_SV_Controller(); - - num_players = NET_SV_NumPlayers(); - - // time to send the client another status packet - - packet = NET_NewPacket(10); - NET_WriteInt16(packet, NET_PACKET_TYPE_WAITING_DATA); - - // include the number of players waiting - - NET_WriteInt8(packet, num_players); - - // send the number of drone clients - - NET_WriteInt8(packet, NET_SV_NumDrones()); - - // indicate whether the client is the controller - - NET_WriteInt8(packet, client == controller); - - // send the player number of this client - - NET_WriteInt8(packet, client->player_number); - - // send the addresses of all players - - for (i=0; i<num_players; ++i) - { - char *addr; - - // name - - NET_WriteString(packet, sv_players[i]->name); - - // address - - addr = NET_AddrToString(sv_players[i]->addr); - - NET_WriteString(packet, addr); - } - - // Send the WAD and dehacked checksums of the controlling client. - - if (controller != NULL) - { - NET_WriteSHA1Sum(packet, controller->wad_sha1sum); - NET_WriteSHA1Sum(packet, controller->deh_sha1sum); - NET_WriteInt8(packet, controller->is_freedoom); - } - else - { - NET_WriteSHA1Sum(packet, client->wad_sha1sum); - NET_WriteSHA1Sum(packet, client->deh_sha1sum); - NET_WriteInt8(packet, client->is_freedoom); - } - - // send packet to client and free - - NET_Conn_SendPacket(&client->connection, packet); - NET_FreePacket(packet); -} - static void NET_SV_PumpSendQueue(net_client_t *client) { net_full_ticcmd_t cmd; int recv_index; + int num_players; int i; int starttic, endtic; @@ -1324,7 +1489,9 @@ static void NET_SV_PumpSendQueue(net_client_t *client) // Check if we can generate a new entry for the send queue // using the data in recvwindow. - for (i=0; i<MAXPLAYERS; ++i) + num_players = 0; + + for (i=0; i<NET_MAXPLAYERS; ++i) { if (sv_players[i] == client) { @@ -1345,6 +1512,19 @@ static void NET_SV_PumpSendQueue(net_client_t *client) return; } + + ++num_players; + } + + // If this is a game with only a single player in it, we might + // be sending a ticcmd set containing 0 ticcmds. This is fine; + // however, there's nothing to stop the game running on ahead + // and never stopping. Don't let the server get too far ahead + // of the client. + + if (num_players == 0 && client->sendseq > recvwindow_start + 10) + { + return; } //printf("SV: have complete ticcmd for %i\n", client->sendseq); @@ -1357,7 +1537,7 @@ static void NET_SV_PumpSendQueue(net_client_t *client) cmd.latency = 0; - for (i=0; i<MAXPLAYERS; ++i) + for (i=0; i<NET_MAXPLAYERS; ++i) { net_client_recv_t *recvobj; @@ -1458,7 +1638,7 @@ static void NET_SV_GameEnded(void) { int i; - server_state = SERVER_WAITING_START; + server_state = SERVER_WAITING_LAUNCH; sv_gamemode = indetermined; for (i=0; i<MAXNETNODES; ++i) @@ -1489,9 +1669,19 @@ static void NET_SV_RunClient(net_client_t *client) if (client->connection.state == NET_CONN_STATE_DISCONNECTED) { - // deactivate and free back - client->active = false; + + // If we were about to start a game, any player disconnecting + // should cause an abort. + + if (server_state == SERVER_WAITING_START && !client->drone) + { + NET_SV_BroadcastMessage("Game startup aborted because " + "player '%s' disconnected.", + client->name); + NET_SV_GameEnded(); + } + free(client->name); NET_FreeAddress(client->addr); @@ -1505,7 +1695,7 @@ static void NET_SV_RunClient(net_client_t *client) NET_SV_GameEnded(); } } - + if (!ClientConnected(client)) { // client has not yet finished connecting @@ -1513,7 +1703,7 @@ static void NET_SV_RunClient(net_client_t *client) return; } - if (server_state == SERVER_WAITING_START) + if (server_state == SERVER_WAITING_LAUNCH) { // Waiting for the game to start @@ -1561,7 +1751,7 @@ void NET_SV_Init(void) NET_SV_AssignPlayers(); - server_state = SERVER_WAITING_START; + server_state = SERVER_WAITING_LAUNCH; sv_gamemode = indetermined; server_initialized = true; } @@ -1578,7 +1768,6 @@ static void UpdateMasterServer(void) if (now - master_resolve_time > MASTER_RESOLVE_PERIOD * 1000) { net_addr_t *new_addr; - printf("Re-resolve master server\n"); new_addr = NET_Query_ResolveMaster(server_context); @@ -1666,17 +1855,26 @@ void NET_SV_Run(void) } } - if (server_state == SERVER_IN_GAME) + switch (server_state) { - NET_SV_AdvanceWindow(); + case SERVER_WAITING_LAUNCH: + break; - for (i=0; i<MAXPLAYERS; ++i) - { - if (sv_players[i] != NULL && ClientConnected(sv_players[i])) + case SERVER_WAITING_START: + CheckStartGame(); + break; + + case SERVER_IN_GAME: + NET_SV_AdvanceWindow(); + + for (i = 0; i < NET_MAXPLAYERS; ++i) { - NET_SV_CheckResends(sv_players[i]); + if (sv_players[i] != NULL && ClientConnected(sv_players[i])) + { + NET_SV_CheckResends(sv_players[i]); + } } - } + break; } } @@ -1740,4 +1938,3 @@ void NET_SV_Shutdown(void) I_Sleep(1); } } - |