static void NET_SV_AdvanceWindow(void) { unsigned int lowtic; int i; if (NET_SV_NumPlayers() <= 0) { return; } lowtic = NET_SV_LatestAcknowledged(); // Advance the recv window until it catches up with lowtic while (recvwindow_start < lowtic) { boolean should_advance; // Check we have tics from all players for first tic in // the recv window should_advance = true; for (i=0; i<NET_MAXPLAYERS; ++i) { if (sv_players[i] == NULL || !ClientConnected(sv_players[i])) { continue; } if (!recvwindow[0][i].active) { should_advance = false; break; } } if (!should_advance) { // The first tic is not complete: ie. we have not // received tics from all connected players. This can // happen if only one player is in the game. break; } // Advance the window memmove(recvwindow, recvwindow + 1, sizeof(*recvwindow) * (BACKUPTICS - 1)); memset(&recvwindow[BACKUPTICS-1], 0, sizeof(*recvwindow)); ++recvwindow_start; //printf("SV: advanced to %i\n", recvwindow_start); } }
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) { M_StringCopy(wait_data.player_names[i], sv_players[i]->name, MAXPLAYERNAME); M_StringCopy(wait_data.player_addrs[i], NET_AddrToString(sv_players[i]->addr), MAXPLAYERNAME); } // 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); }
void NET_SV_SendQueryResponse(net_addr_t *addr) { net_packet_t *reply; net_querydata_t querydata; int p; // Version querydata.version = PACKAGE_STRING; // Server state querydata.server_state = server_state; // Number of players/maximum players querydata.num_players = NET_SV_NumPlayers(); querydata.max_players = NET_SV_MaxPlayers(); // Game mode/mission querydata.gamemode = sv_gamemode; querydata.gamemission = sv_gamemission; //! // @category net // @arg <name> // // When starting a network server, specify a name for the server. // p = M_CheckParmWithArgs("-servername", 1); if (p > 0) { querydata.description = myargv[p + 1]; } else { querydata.description = "Unnamed server"; } // Send it and we're done. NET_Log("server: sending query response to %s", NET_AddrToString(addr)); reply = NET_NewPacket(64); NET_WriteInt16(reply, NET_PACKET_TYPE_QUERY_RESPONSE); NET_WriteQueryData(reply, &querydata); NET_SendPacket(addr, reply); NET_FreePacket(reply); }
static void NET_SV_ParseLaunch(net_packet_t *packet, net_client_t *client) { net_packet_t *launchpacket; int num_players; unsigned int i; NET_Log("server: processing launch packet"); // Only the controller can launch the game. if (client != NET_SV_Controller()) { NET_Log("server: error: this client isn't the controller, %d != %d", client, NET_SV_Controller()); return; } // Can only launch when we are in the waiting state. if (server_state != SERVER_WAITING_LAUNCH) { NET_Log("server: error: not in waiting launch state, state=%d", server_state); return; } // Forward launch on to all clients. NET_Log("server: sending launch to all clients"); NET_SV_AssignPlayers(); num_players = NET_SV_NumPlayers(); for (i=0; i<MAXNETNODES; ++i) { if (!ClientConnected(&clients[i])) continue; 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; }
static void NET_SV_ParseGameStart(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 return; } if (!NET_ReadSettings(packet, &settings)) { // Malformed packet return; } // Check the game settings are valid if (!NET_ValidGameSettings(sv_gamemode, sv_gamemission, &settings)) { return; } if (server_state != SERVER_WAITING_START) { // Can only start a game if we are in the waiting start state. return; } // Assign player numbers NET_SV_AssignPlayers(); // Check if anyone is recording a demo and set lowres_turn if so. settings.lowres_turn = false; for (i=0; i<MAXPLAYERS; ++i) { if (sv_players[i] != NULL && sv_players[i]->recording_lowres) { settings.lowres_turn = true; } } nowtime = I_GetTimeMS(); // Send start packets to each connected node for (i=0; i<MAXNETNODES; ++i) { if (!ClientConnected(&clients[i])) continue; clients[i].last_gamedata_time = nowtime; 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); } // Change server state server_state = SERVER_IN_GAME; sv_settings = settings; memset(recvwindow, 0, sizeof(recvwindow)); recvwindow_start = 0; }
static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client, 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; md5_digest_t deh_md5sum, wad_md5sum; char *player_name; char *client_version; int i; // read the magic number if (!NET_ReadInt32(packet, &magic)) { return; } if (magic != NET_MAGIC_NUMBER) { // invalid magic number return; } // Check the client version is the same as the server client_version = NET_ReadString(packet); if (client_version == NULL) { return; } if (strcmp(client_version, PACKAGE_STRING) != 0) { //! // @category net // // When running a netgame server, ignore version mismatches between // the server and the client. Using this option may cause game // desyncs to occur, or differences in protocol may mean the netgame // will simply not function at all. // if (M_CheckParm("-ignoreversion") == 0) { NET_SV_SendReject(addr, "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_ReadMD5Sum(packet, wad_md5sum) || !NET_ReadMD5Sum(packet, deh_md5sum) || !NET_ReadInt8(packet, &is_freedoom)) { return; } if (!NET_ValidGameMode(cl_gamemode, cl_gamemission)) { return; } // read the player's name player_name = NET_ReadString(packet); if (player_name == NULL) { return; } // received a valid SYN // not accepting new connections? if (server_state != SERVER_WAITING_START) { NET_SV_SendReject(addr, "Server is not currently accepting connections"); return; } // allocate a client slot if there isn't one already if (client == NULL) { // find a slot, or return if none found for (i=0; i<MAXNETNODES; ++i) { if (!clients[i].active) { client = &clients[i]; break; } } if (client == NULL) { return; } } else { // If this is a recently-disconnected client, deactivate // to allow immediate reconnection if (client->connection.state == NET_CONN_STATE_DISCONNECTED) { client->active = false; } } // New client? if (!client->active) { int num_players; // Before accepting a new client, check that there is a slot // free NET_SV_AssignPlayers(); num_players = NET_SV_NumPlayers(); if ((!cl_drone && num_players >= MAXPLAYERS) || NET_SV_NumClients() >= MAXNETNODES) { NET_SV_SendReject(addr, "Server is full!"); return; } // TODO: Add server option to allow rejecting clients which // set lowres_turn. This is potentially desirable as the // presence of such clients affects turning resolution. // Adopt the game mode and mission of the first connecting client if (num_players == 0 && !cl_drone) { sv_gamemode = cl_gamemode; sv_gamemission = cl_gamemission; } // Save the MD5 checksums memcpy(client->wad_md5sum, wad_md5sum, sizeof(md5_digest_t)); memcpy(client->deh_md5sum, deh_md5sum, sizeof(md5_digest_t)); client->is_freedoom = is_freedoom; // Check the connecting client is playing the same game as all // the other clients if (cl_gamemode != sv_gamemode || cl_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; } if (client->connection.state == NET_CONN_STATE_WAITING_ACK) { // force an acknowledgement client->connection.last_send_time = -1; } }
static void NET_SV_RunClient(net_client_t *client) { // Run common code NET_Conn_Run(&client->connection); if (client->connection.state == NET_CONN_STATE_DISCONNECTED && client->connection.disconnect_reason == NET_DISCONNECT_TIMEOUT) { NET_SV_BroadcastMessage("Client '%s' timed out and disconnected", client->name); } // Is this client disconnected? if (client->connection.state == NET_CONN_STATE_DISCONNECTED) { // deactivate and free back client->active = false; free(client->name); NET_FreeAddress(client->addr); // Are there any clients left connected? If not, return the // server to the waiting-for-players state. // // Disconnect any drones still connected. if (NET_SV_NumPlayers() <= 0) { NET_SV_GameEnded(); } } if (!ClientConnected(client)) { // client has not yet finished connecting return; } if (server_state == SERVER_WAITING_START) { // Waiting for the game to start // Send information once every second if (client->last_send_time < 0 || I_GetTimeMS() - client->last_send_time > 1000) { NET_SV_SendWaitingData(client); client->last_send_time = I_GetTimeMS(); } } if (server_state == SERVER_IN_GAME) { NET_SV_PumpSendQueue(client); NET_SV_CheckDeadlock(client); } }
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_WriteMD5Sum(packet, controller->wad_md5sum); NET_WriteMD5Sum(packet, controller->deh_md5sum); NET_WriteInt8(packet, controller->is_freedoom); } else { NET_WriteMD5Sum(packet, client->wad_md5sum); NET_WriteMD5Sum(packet, client->deh_md5sum); NET_WriteInt8(packet, client->is_freedoom); } // send packet to client and free NET_Conn_SendPacket(&client->connection, packet); NET_FreePacket(packet); }
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. sv_settings.lowres_turn = false; for (i = 0; i < NET_MAXPLAYERS; ++i) { if (sv_players[i] != NULL && sv_players[i]->recording_lowres) { 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; } } nowtime = I_GetTimeMS(); // Send start packets to each connected node for (i = 0; i < MAXNETNODES; ++i) { if (!ClientConnected(&clients[i])) continue; clients[i].last_gamedata_time = nowtime; startpacket = NET_Conn_NewReliable(&clients[i].connection, NET_PACKET_TYPE_GAMESTART); sv_settings.consoleplayer = clients[i].player_number; NET_WriteSettings(startpacket, &sv_settings); } // Change server state server_state = SERVER_IN_GAME; memset(recvwindow, 0, sizeof(recvwindow)); recvwindow_start = 0; }
static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client, net_addr_t *addr) { unsigned int magic; net_connect_data_t data; net_packet_t *reply; net_protocol_t protocol; char *player_name; char *client_version; int num_players; int i; NET_Log("server: processing SYN packet"); // Read the magic number and check it is the expected one. if (!NET_ReadInt32(packet, &magic)) { NET_Log("server: error: no magic number for SYN"); return; } switch (magic) { case NET_MAGIC_NUMBER: break; case NET_OLD_MAGIC_NUMBER: NET_Log("server: error: client using old magic number: %d", magic); NET_SV_SendReject(addr, "You are using an old client version that is not supported by " "this server. This server is running " PACKAGE_STRING "."); return; default: NET_Log("server: error: wrong magic number: %d", magic); return; } // Read the client version string. We actually now only use this when // sending a reject message, as we only reject if we can't negotiate a // common protocol (below). client_version = NET_ReadString(packet); if (client_version == NULL) { NET_Log("server: error: no version from client"); return; } // Read the client's list of accepted protocols. Net play between forks // of Chocolate Doom is accepted provided that they can negotiate a // common accepted protocol. protocol = NET_ReadProtocolList(packet); if (protocol == NET_PROTOCOL_UNKNOWN) { char reject_msg[256]; M_snprintf(reject_msg, sizeof(reject_msg), "Version mismatch: server version is: " PACKAGE_STRING "; " "client is: %s. No common compatible protocol could be " "negotiated.", client_version); NET_SV_SendReject(addr, reject_msg); NET_Log("server: error: no common protocol"); return; } // Read connect data, and check that the game mode/mission are valid // and the max_players value is in a sensible range. if (!NET_ReadConnectData(packet, &data)) { NET_Log("server: error: failed to read connect data"); return; } if (!D_ValidGameMode(data.gamemission, data.gamemode) || data.max_players > NET_MAXPLAYERS) { NET_Log("server: error: invalid connect data, max_players=%d, " "gamemission=%d, gamemode=%d", data.max_players, data.gamemission, data.gamemode); return; } // Read the player's name player_name = NET_ReadString(packet); if (player_name == NULL) { NET_Log("server: error: failed to read player name"); return; } // At this point we have received a valid SYN. // Not accepting new connections? if (server_state != SERVER_WAITING_LAUNCH) { NET_Log("server: error: not in waiting launch state, server_state=%d", server_state); NET_SV_SendReject(addr, "Server is not currently accepting connections"); return; } // Before accepting a new client, check that there is a slot free. NET_SV_AssignPlayers(); num_players = NET_SV_NumPlayers(); if ((!data.drone && num_players >= NET_SV_MaxPlayers()) || NET_SV_NumClients() >= MAXNETNODES) { NET_Log("server: no more players, num_players=%d, max=%d", num_players, NET_SV_MaxPlayers()); NET_SV_SendReject(addr, "Server is full!"); return; } // TODO: Add server option to allow rejecting clients which set // lowres_turn. This is potentially desirable as the presence of such // clients affects turning resolution. // Adopt the game mode and mission of the first connecting client: if (num_players == 0 && !data.drone) { sv_gamemode = data.gamemode; sv_gamemission = data.gamemission; NET_Log("server: new game, mode=%d, mission=%d", sv_gamemode, sv_gamemission); } // Check the connecting client is playing the same game as all // the other clients if (data.gamemode != sv_gamemode || data.gamemission != sv_gamemission) { char msg[128]; NET_Log("server: wrong mode/mission, %d != %d || %d != %d", data.gamemode, sv_gamemode, data.gamemission, sv_gamemission); M_snprintf(msg, sizeof(msg), "Game mismatch: server is %s (%s), client is %s (%s)", D_GameMissionString(sv_gamemission), D_GameModeString(sv_gamemode), D_GameMissionString(data.gamemission), D_GameModeString(data.gamemode)); NET_SV_SendReject(addr, msg); return; } // Allocate a client slot if there isn't one already if (client == NULL) { // find a slot, or return if none found for (i=0; i<MAXNETNODES; ++i) { if (!clients[i].active) { client = &clients[i]; break; } } if (client == NULL) { return; } } else { // If this is a recently-disconnected client, deactivate // to allow immediate reconnection if (client->connection.state == NET_CONN_STATE_DISCONNECTED) { client->active = false; } } // Client already connected? if (client->active) { NET_Log("server: client is already initialized (duplicate SYN?)"); return; } // Activate, initialize connection NET_SV_InitNewClient(client, addr, protocol); // Save the SHA1 checksums and other details. 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; client->name = M_StringDuplicate(player_name); client->recording_lowres = data.lowres_turn; client->drone = data.drone; client->player_class = data.player_class; // Send a reply back to the client, indicating a successful connection // and specifying the protocol that will be used for communications. reply = NET_Conn_NewReliable(&client->connection, NET_PACKET_TYPE_SYN); NET_WriteString(reply, PACKAGE_STRING); NET_WriteProtocol(reply, protocol); }
static void NET_SV_RunClient(net_client_t *client) { // Run common code NET_Conn_Run(&client->connection); if (client->connection.state == NET_CONN_STATE_DISCONNECTED && client->connection.disconnect_reason == NET_DISCONNECT_TIMEOUT) { NET_Log("server: client at %s timed out", NET_AddrToString(client->addr)); NET_SV_BroadcastMessage("Client '%s' timed out and disconnected", client->name); } // Is this client disconnected? if (client->connection.state == NET_CONN_STATE_DISCONNECTED) { 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_ReleaseAddress(client->addr); // Are there any clients left connected? If not, return the // server to the waiting-for-players state. // // Disconnect any drones still connected. if (NET_SV_NumPlayers() <= 0) { NET_Log("server: no player clients left, game ended"); NET_SV_GameEnded(); } } if (!ClientConnected(client)) { // client has not yet finished connecting return; } if (server_state == SERVER_WAITING_LAUNCH) { // Waiting for the game to start // Send information once every second if (client->last_send_time < 0 || I_GetTimeMS() - client->last_send_time > 1000) { NET_SV_SendWaitingData(client); client->last_send_time = I_GetTimeMS(); } } if (server_state == SERVER_IN_GAME) { NET_SV_PumpSendQueue(client); NET_SV_CheckDeadlock(client); } }