/// gathering player inputs, networking or loopback, replay files /* Networking * * * client/server vs peer to peer * * Peer to peer is best for only a few players. It minimizes the lag * there, but each client's traffic can get quite high for many * players. The player's packetfilter (if any) has to allow incoming * connections, and in case of NAT to forward them correctly. * * Client/server minimizes the traffic for the clients, but adds * somewhat lag overhead. Packetfilters usually allow all outbound * traffic, so the players will have no NAT troubles. * * * tpc vs udp * * Udp seems the better choice, as it will not block in case a packet * got lost. The trick is to always send redundant information, so the * clients will hopefully get all data even if a packet was lost. In * the worst case the a client is missing some player's data and has * to ask for it (or ack messages have to be sent...) * * Tcp has the advantages that packet loss is handled automatically * (but slowly), and the data is received in the right order. * * For more info, look at the documentation of the Stratagus project, * the comment in stratagus/src/network/network.c, for NAT issues see * http://midcom-p2p.sourceforge.net/ * * Implementation: * * There is a mostly line based client-server protocol to find players * and setup UDP connections between them (for details see NAT link * above). Slow game messages will go over this link, like "new player * added", "is anybody there", "I need this levelfile", "what was the * final data packet before player 3 disconnected" or "hey your * version is outdated". * * Game messages (player inputs, lag announcements, checksums) are * sent directly via UDP. When the clients start sending UDP to each * other, the first packets will probably get lost because of NAT. * * * How to join a network game? * * [IMPLEMENTED] * Simple version: Games have to be started simultanously, and there's * no need to send the state of a whole running game at all, just * player input. Game type and random number generator are synced. * * [PLANNED] * In-game join. Special masses act as "portal", they (from the * player's point of view) are an exit from the current level, and * other players may drop in. Implementation: when a player joins, the * whole game is saved, sent to the new player, and both old and new * players restart from scratch with this savegame. Additionally, a * new player might bring some new masses into the level (must be * distributed too, inserted into the savegame before loading). * * The server might send some synced information about external games * that are running. This info may influence the level (portal mass * gets bright if a player is on the other side, minimap of related * games running, etc) * * [THOUGHTS] * Client/server would also allow the server to have a monopoly for * sending packets, always just sending the most recent input data * from all players. A player could lag away happily without * disturbing those with good connections... but players directly * connected to the server would have a clear game advantage. * */ #include "stdlib.h" #include #include #include #include "network.h" #include "config.h" #include "input.h" #include "engine.h" #include "level.h" #include "main.h" #include "debug.h" #include "stringio.h" #include "SDL_endian.h" #ifndef DISABLE_SDLNET #include "SDL_net.h" #include "gethostname.h" #endif typedef struct { #ifndef DISABLE_SDLNET // socket to listen on UDPsocket udpSocket; int myUdpPort; // packets are reused because they remember the dst host/port once set UDPpacket * dst; UDPpacket * dst2; // only used while building the connection enum { UDPSTAT_IGNORE=0, UDPSTAT_UNVERIFIED, UDPSTAT_ESTABLISHED } status; #endif } NetworkPlayer; NetworkPlayer networkPlayer[MAX_PLAYERS]; // FIXME: to check player validity the those statement are used arbitrarlily: // (udpSocket != NULL), (status != UDPSTAT_IGNORE), player[id].exists int localPlayer; struct { int button1, button2; char dir; } oldPlayerInput; // current network frame Uint8 networkFrame; Uint8 networkLag; // laggy is decremented by one each networkFrame, and incremented by // LAGGY_STEP each networkFrame if game lags. int laggy; #define LAGGY_START 340 #define LAGGY_MAX 400 #define LAGGY_STEP 50 int hadToWait; int hadToWaitSinceChecksum; Uint32 lastChecksumTime; Uint8 lastChecksum; #ifndef DISABLE_SDLNET TCPsocket serverSocket = NULL; #endif #define UDP_SEND_MAX_FRAMES 16 #define UDP_SEND_MAX_BYTES 20 FILE * logfile = NULL; int gamePrepared; #define SERVERLINES_CACHED 50 // [0] is returned to caller; last entry always NULL char * global_GetServerLineBuffer[SERVERLINES_CACHED]; /* Commands are done as small strings: [a-z] button id ... toggled C4F checksum is 4F L player complains about lag For example player 3 frame 60 might have sent the command "C22L". Or more frequently, the command "". Packet: >0F2C4FL L aL L L ||\|^^^^| ^ ^ ^^ previous frame (two spaces: empty frame between) || |Data| || | || + Frame number of first datastring (hex, 0-255) |+ From player id 0 + Packet type: playerinput Log: > 0 1L 2C4FL | ||^next player | |+Data (Lag) | +player number +Playerinput Prefix Note: We either receive all commands from a given frame+player, or none. Thus the data must exactly match if a duplicate is received. */ // the local queue, circular; NULL means not received yet char * queue[256][MAX_PLAYERS]; #ifndef DISABLE_SDLNET char * malloc_TCPGetLine(TCPsocket sock, int blocking) { char * buf; int size; int pos; if (!blocking) { int numReady; SDLNet_SocketSet set = NULL; set = SDLNet_AllocSocketSet(1); SDLNet_TCP_AddSocket(set, sock); numReady = SDLNet_CheckSockets(set, 0); SDLNet_FreeSocketSet(set); set = NULL; if (numReady == -1) Die("SDLNet_CheckSockets: %s\n", SDLNet_GetError()); if (numReady == 0) return NULL; } size = 8; buf = malloc(size); pos = 0; while (pos == 0 || buf[pos-1] != '\n') { int result; // note, small bug here: an incomplete line might make the // function block even when blocking=0 result = SDLNet_TCP_Recv(sock, buf+pos, 1); if (result == 0) Die("GetTCPLine: Server connection lost.\n"); if (result <= 0) Die("GetTCPLine: SDLNet_TCP_Recv: %s\n", SDLNet_GetError()); assert(result == 1); pos++; if (size == pos) { size *= 2; buf = realloc(buf, size); } } buf[pos-1] = 0; return buf; } void TCPSendLine(TCPsocket sock, char * s) { int len; char * buf; len = strlen(s); buf = malloc(len+1); strcpy(buf, s); assert(buf[len] == '\0'); buf[len++] = '\n'; if (SDLNet_TCP_Send(sock, buf, len) < len) Die("SDLNet_TCP_Send: %s\n", SDLNet_GetError()); xfree(buf); } char * GetServerLine(int blocking) { int i; xfree(global_GetServerLineBuffer[0]); for (i=1; global_GetServerLineBuffer[i]; i++) { global_GetServerLineBuffer[i-1] = global_GetServerLineBuffer[i]; } global_GetServerLineBuffer[i-1] = NULL; if (!global_GetServerLineBuffer[0]) { global_GetServerLineBuffer[0] = malloc_TCPGetLine(serverSocket, blocking); } if (global_GetServerLineBuffer[0]) { DebugLevel(2, "[SERVER] %s\n", global_GetServerLineBuffer[0]); } return global_GetServerLineBuffer[0]; } void SendServerLine(char * s) { DebugLevel(2, "[CLIENT] %s\n", s); TCPSendLine(serverSocket, s); } // defer any unrelated messages char * malloc_GetServerData(char * request) { char * buf; int read, i, content_length; DebugLevel(2, "[DATA] "); SendServerLine(request); buf = malloc_TCPGetLine(serverSocket, 1); while (!startswith(buf, "DATA ")) { DebugLevel(2, "(process serverline later: %s)\n", buf); for (i=1; global_GetServerLineBuffer[i]; i++) ; if (i == SERVERLINES_CACHED-1) Die("Server line cache buffer too small\n"); global_GetServerLineBuffer[i] = buf; buf = malloc_TCPGetLine(serverSocket, 1); } content_length = atoi(strword(buf, 1)); xfree(buf); if (content_length == 0) { DebugLevel(2, "[DATA] [SERVER] no data\n", buf); return NULL; } buf = malloc(content_length + 1); read = 0; while (read < content_length) { i = SDLNet_TCP_Recv(serverSocket, buf+read, content_length-read); if (i <= 0) Die("GetServerData: SDLNet_TCP_Recv: %s\n", SDLNet_GetError()); read += i; } if (read > content_length) Die("Read too much data\n"); buf[content_length] = 0; DebugLevel(2, "[DATA] [SERVER] %d bytes received\n", content_length); return buf; } // non-blocking; returns NULL or a zero-terminated string char * malloc_UDPGetPacket(UDPsocket sock) { char * buf; UDPpacket * pack; pack = SDLNet_AllocPacket(1024); if (SDLNet_UDP_Recv(sock, pack) == 1) { if (pack->len == 1024) { pack->data[1024-1] = 0; } else { pack->data[pack->len] = 0; } buf = malloc_strcpy(pack->data); } else { buf = NULL; } SDLNet_FreePacket(pack); return buf; } void ConnectAndJoin() { IPaddress serverIP; UDPpacket * pack; int id, ready, start, randseed; char * buf; char * error; if (SDLNet_ResolveHost(&serverIP,config.server,config.serverPort) == -1) Die("SDLNet_ResolveHost: %s\n", SDLNet_GetError()); serverSocket = SDLNet_TCP_Open(&serverIP); if (!serverSocket) Die("SDLNet_TCP_Open: %s\n", SDLNet_GetError()); buf = malloc(256); sprintf(buf, "MASS %s", VERSIONNAME); SendServerLine(buf); xfree(buf); buf = GetServerLine(1); DebugLevel(1, "Got server greeting:\n%s\n", buf); if (!startswith(buf, "MASS ")) Die("\nWell, the greeting is not quite what it should look like.\n"); // try to join whatever is going on, if anything SendServerLine("JOIN"); buf = GetServerLine(1); if (!startswith(buf, "CREATE")) { // need to create a new game randseed = rand() % 1024; buf = malloc(256); sprintf(buf, "CREATE %.200s %d %d", config.levelfile, config.players, randseed); SendServerLine(buf); xfree(buf); buf = GetServerLine(1); if (!startswith(buf, "ID")) Die("CREATE did not work\n"); } else { // parse creation arguments char * c; c = strtok(buf, " "); if (!c) Die("Short CREATE command\n"); #define NEXT { c = strtok(NULL, " "); if (!c) Die("Short CREATE command\n"); } NEXT; xfree(config.levelfile); config.levelfile = malloc_strcpy(c); if (!strlen(config.levelfile)) Die("Invalid CREATE\n"); NEXT; config.players = atoi(c); NEXT; randseed = atoi(c); buf = GetServerLine(1); if (!startswith(buf, "ID ")) Die("Expected ID from server\n"); } LogEngineLine(0, "RandSeed", int2str(randseed)); localPlayer = atoi(strword(buf, 1)); if (localPlayer < 0 || localPlayer >= MAX_PLAYERS) Die("Invalid player id received.\n"); DebugLevel(1, "Successfully joined the game.\n"); DebugLevel(1, "You got player id %d.\n", localPlayer); // detect map troubles early error = LoadLevel(config.levelfile); if (error) { Die("LoadLevel(%s) failed: %s\n", config.levelfile, error); } DebugLevel(2, "Full game checksum is %d\n", (int)FullChecksum()); if (localPlayer == 0) { // Set server settings buf = malloc_strcpy("MaxPlayers "); buf = realloc_strcat(buf, int2str(config.players)); SendServerLine(buf); xfree(buf); } DebugLevel(1, "Waiting...\n"); pack = SDLNet_AllocPacket(1024); ready = 0; start = 0; while (!(ready && start)) { int event; event = 0; buf = GetServerLine(0); if (buf) { event = 1; if (buf[0] == '+') { // add player NetworkPlayer * p; int tries; id = atoi(strword(buf, 1)); if (id < 0 || id >= MAX_PLAYERS || id == localPlayer) Die("Invalid player id received.\n"); p = &networkPlayer[id]; if (p->udpSocket) Die("Server added an already existing player\n"); DebugLevel(1, "Adding new player id %d.\n", id); // player UDP setup DebugLevel(2, "Opening new UDP socket:\n"); // 1. open socket to listen on p->myUdpPort = config.udpPort - 1; tries = 0; while (!p->udpSocket) { p->myUdpPort++; if (config.udpPort == 0) p->myUdpPort = rand()%60000 + 2000; if (tries++ > 100) Die("SDLNet_UDP_Open: %s\n", SDLNet_GetError()); p->udpSocket = SDLNet_UDP_Open(p->myUdpPort); } // 2. send echo request to get own address // reuse resolved server ip/port, this time udp pack->address = serverIP; strcpy(pack->data, "ECHO"); pack->len = strlen(pack->data); tries = 20; while (1) { if (tries-- == 0) Die("Did not get an UDP echo reply from server after 20 trials.\n"); DebugLevel(2, "Sending UDP echo request.\n"); if (SDLNet_UDP_Send(p->udpSocket, -1, pack) != 1) Die("SDLNet_UDP_Send: %s\n", SDLNet_GetError()); SDL_Delay(500); if (SDLNet_UDP_Recv(p->udpSocket, pack) == 1) { if (pack->len < 1000) { // just accept it, the other client's problem if it was garbage // make it a string pack->data[pack->len] = 0; break; } } } // 3. send public and private ip/port to server { // private // This is needed if two players are behind the same NAT, // and the server does not see their private address, and // the NAT doesn't have this loopback thingy (according to // http://midcom-p2p.sourceforge.net/ only 50% of the NATs // can do this) // SDLNet seems to have no way to find out the local ip. //myIP = SDLNet_UDP_GetPeerAddress(p->udpSocket, -1); IPaddress ip; Uint8 * host; if (SDLNet_ResolveHost(&ip, GetHostName(), 0) != 0) Die("Could not resolve own host name '%s': %s\n", GetHostName(), SDLNet_GetError()); host = (Uint8*) &ip.host; buf = malloc(256); sprintf(buf, "UDP %d %d.%d.%d.%d %d", id, host[0], host[1], host[2], host[3], p->myUdpPort); DebugLevel(2, "Listening on %s for player %d.\n", buf, id); } buf = realloc_strcat(buf, " "); // public (as received in the echo reply) buf = realloc_strcat(buf, (char*)pack->data); SendServerLine(buf); xfree(buf); } else if (buf[0] == '=') { // 4. server tells other peer's public and private ip/port int id; NetworkPlayer * p; char * host_private; char * port_private; char * host_public; char * port_public; Uint8 * cli; id = atoi(strword(buf, 1)); if (id < 0 || id >= MAX_PLAYERS || id == localPlayer) Die("Invalid player id received.\n"); p = &networkPlayer[id]; if (!p->udpSocket) Die("Server told address of player %d before adding that player\n", id); if (p->status != UDPSTAT_IGNORE) Die("Got connection data twice for player %d\n", id); p->status = UDPSTAT_UNVERIFIED; host_private = strword(buf, 2); port_private = strword(buf, 3); host_public = strword(buf, 4); port_public = strword(buf, 5); if (!strlen(port_public) || strlen(strword(buf, 6))) Die("Received invalid UDP announcement from player %d\n", id); port_private[-1] = 0; host_public[-1] = 0; port_public[-1] = 0; if (p->dst || p->dst2) Die("network.c: dst and dst2 should be NULL\n"); p->dst = SDLNet_AllocPacket(1024); if (SDLNet_ResolveHost(&p->dst->address, host_private, atoi(port_private)) == -1) Die("SDLNet_ResolveHost: %s\n", SDLNet_GetError()); p->dst2 = SDLNet_AllocPacket(1024); if (SDLNet_ResolveHost(&p->dst2->address, host_public , atoi(port_public) ) == -1) Die("SDLNet_ResolveHost: %s\n", SDLNet_GetError()); cli = (Uint8*) &p->dst2->address.host; if (cli[0] == 127 || cli[0] == 10 || (cli[0] == 172 && cli[1] == 16) || (cli[0] == 192 && cli[1] == 168)) { // The other player has a private "public" address. He is // probably in the NAT behind the gameserver, so use the // gameserver's IP instead. Some NATs do an 1:1 mapping of // the udp ports when possible, which is our only hope now. // (besides the hope that it's a local game) p->dst2->address.host = serverIP.host; } } else if (streq(buf, "START")) { if (!ready) Die("Server sent START but I was not ready\n"); start = 1; } } // check for new UDP packets for (id=0; idstatus != UDPSTAT_IGNORE) { if (SDLNet_UDP_Recv(p->udpSocket, pack) == 1) { event = 1; if (pack->len == 2 && pack->data[0] == (Uint8)'*' && pack->data[1] == id) { DebugLevel(2, "UDP player %d established (%s:%d)\n", id, SDLNet_ResolveIP(&pack->address), pack->address.port); p->status = UDPSTAT_ESTABLISHED; // now we know for sure where to send to SDLNet_FreePacket(p->dst2); p->dst2 = NULL; p->dst->address = pack->address; } else { DebugLevel(2, "Dropped unrelated UDP packet of size %d\n", pack->len); } } } } if (!event) { // send UDP pings, then sleep a bit for (id=0; idstatus != UDPSTAT_IGNORE) { if (p->dst) { p->dst->data[0] = (Uint8)'*'; p->dst->data[1] = localPlayer; p->dst->len = 2; if (SDLNet_UDP_Send(p->udpSocket, -1, p->dst) != 1) Die("SDLNet_UDP_Send: %s\n", SDLNet_GetError()); } if (p->dst2) { p->dst2->data[0] = (Uint8)'*'; p->dst2->data[1] = localPlayer; p->dst2->len = 2; if (SDLNet_UDP_Send(p->udpSocket, -1, p->dst2) != 1) Die("SDLNet_UDP_Send: %s\n", SDLNet_GetError()); } } } SDL_Delay(500); } if (!ready) { int count = 0; for (id=0; iddst) { SDLNet_FreePacket(p->dst); p->dst = NULL; } if (p->dst2) { SDLNet_FreePacket(p->dst2); p->dst2 = NULL; } if (p->udpSocket) { SDLNet_UDP_Close(p->udpSocket); p->udpSocket = NULL; } p->status = UDPSTAT_IGNORE; } #endif for (i=0; i' && strlen(buf) >= 4) { int id2; char * c = buf + 1; DebugLevel(3, "Got player UDP packet '%s'\n", buf); id2 = *(c++) - '0'; if (id2 < 0 || id2 >= MAX_PLAYERS) Die("UDP packet with invalid player id.\n"); u = hexchr2int(*(c++))*16; u += hexchr2int(*(c++)); do { char * c2; c2 = strchr(c, ' '); if (c2) *(c2++) = 0; //printf("Parsing part: '%s' (for id2=%d, u=%d)\n", c, id2, (int)u); if (!queue[u][id2]) { queue[u][id2] = malloc_strcpy(c); } else { if (!streq(c, queue[u][id2])) Die("UDP packet duplicate with different data received.\n."); } u--; c = c2; } while (c && *c); } else { DebugLevel(2, "Dropped unrelated UDP packet '%s'\n", buf); } xfree(buf); } } } while ((buf = GetServerLine(0)) != NULL) { // parse server commands printf("Server command ignored:\n%s\n", buf); } // 2. still something missing? missing = 0; for (id=0; id= 0 && id < MAX_PLAYERS); count = 0; len = 0; data = malloc(5); data[len++] = '>'; data[len++] = '0' + id; data[len++] = int2hexc(u / 16); data[len++] = int2hexc(u % 16); data[len] = 0; assert(queue[u][id]); while (1) { if (!queue[u][id]) return data; len = len + 1 + strlen(queue[u][id]) + 1; if (len >= UDP_SEND_MAX_BYTES) return data; if (count >= UDP_SEND_MAX_FRAMES) return data; data = realloc(data, len); if (count > 0) strcat(data, " "); strcat(data, queue[u][id]); u--; count++; } } // send queued data from a player for frame nFrame and earlier // to all peers void SendPackets(Uint8 nFrame, int player) { #ifdef DISABLE_SDLNET if (strlen(config.replay)) return; Die("Internal error: SendPackets() with SDL_net disabled\n"); #else char * data; int id; if (strlen(config.replay)) return; assert(player != -1); data = malloc_MakePacket(nFrame, player); DebugLevel(3, "Sending packet '%s'\n", data); for (id=0; iddata, data, networkPlayer[id].dst->maxlen); networkPlayer[id].dst->len = MIN(strlen(data), networkPlayer[id].dst->maxlen); if (SDLNet_UDP_Send(networkPlayer[id].udpSocket, -1, networkPlayer[id].dst) != 1) { DebugLevel(1, "SDLNet_UDP_Send (player %d): %s\n", id, SDLNet_GetError()); } } } xfree(data); #endif } void ParseNewData(Uint8 nFrame) { int laggers = 0; int checksums = 0; Uint8 checksum = 0; int id; for (id=0; id= 0) laggy += LAGGY_STEP; fprintf(logfile, ">"); for (id=0; idpressed) { data[len++] = 'a'; oldPlayerInput.button1 = key.button1->pressed; } if (oldPlayerInput.button2 != key.button2->pressed) { data[len++] = 'b'; oldPlayerInput.button2 = key.button2->pressed; } { char dir; int movex = 0, movey = 0; if (key.left->pressed) movex--; if (key.up->pressed) movey--; if (key.right->pressed) movex++; if (key.down->pressed) movey++; dir = 0; if (movex == 0 && movey == 0) dir = '5'; if (movex == 0 && movey == +1) dir = '2'; if (movex == 0 && movey == -1) dir = '8'; if (movex == +1 && movey == 0) dir = '6'; if (movex == +1 && movey == +1) dir = '3'; if (movex == +1 && movey == -1) dir = '9'; if (movex == -1 && movey == 0) dir = '4'; if (movex == -1 && movey == +1) dir = '1'; if (movex == -1 && movey == -1) dir = '7'; assert(dir); if (dir != oldPlayerInput.dir) { data[len++] = dir; oldPlayerInput.dir = dir; } } data[len++] = 0; // queue the generated packet locally assert(localPlayer != -1); assert(queue[nFrame][localPlayer] == NULL); queue[nFrame][localPlayer] = malloc_strcpy(data); } void ReceivePacketsFromLogfile(Uint8 nFrame) { // parse logfile until next frame char * line; char * error; while (1) { line = malloc_freadline(logfile); if (!line) { DebugLevel(1, "Logfile playback done, pausing the game.\n"); paused = 1; return; } if (line[0] == '>') { int id; char * tok; // finish pending engine statement EngineLine(0, NULL, NULL); if (strlen(line) < 3) Die("Short logfile.\n"); tok = strtok(line + 2, " "); while (tok) { id = tok[0] - '0'; if (id < 0 || id >= MAX_PLAYERS) Die("Logfile with invalid player id.\n"); if (!player[id].exists) Die("Logfile with nonexisting player id.\n"); if (queue[nFrame][id]) Die("Logfile contains duplicate (nFrame=%d, player=%d):\n", nFrame); queue[nFrame][id] = malloc_strcpy(tok + 1); tok = strtok(NULL, " "); } // check for missing / extra data is done when parsing return; } else if (startswith(line, "LocalPlayer")) { localPlayer = atoi(strword(line, 1)); if (localPlayer < 0 || localPlayer >= MAX_PLAYERS) Die("bad local player id"); DebugLevel(2, "Local player id %d\n", localPlayer); } else { // engine command error = ParseLine(line, EngineLine); if (error) Die("Engine error in logfile: %s\n%s\n", error, line); } } } void PrepareGame() { DebugLevel(2, "Full game checksum before PrepareGame() is %d\n", (int)FullChecksum()); if (!strlen(config.replay)) { logfile = fopen("mass.rep", "w"); if (!logfile) Die("Could not write logfile mass.rep\n"); fprintf(logfile, "# Mass %s replay file\n", VERSIONNAME); } if (strlen(config.replay)) { printf("*** Replay mode\n"); logfile = fopen(config.replay, "r"); if (!logfile) Die("Could not open desired logfile '%s'\n", config.replay); } else if (strlen(config.server)) { printf("*** Multiplayer mode\n"); #ifdef DISABLE_SDLNET Die("This binary was compiled with network support (SDL_net) disabled.\n"); #else ConnectAndJoin(); #endif } else { char * error; // singleplayer printf("*** Singleplayer mode\n"); LogEngineLine(0, "RandSeed", int2str(rand() % 1024)); LogEngineLine(0, "AddPlayer", "0"); LogLine(0, "LocalPlayer", "0"); error = LoadLevel(config.levelfile); if (error) Die("LoadLevel failed: %s\n", error); } assert(localPlayer != -1); gamePrepared = 1; } // called from main() every engine frame void UpdatePlayerInput(int currentFrame) { if (currentFrame % 8 != 0) return; if (!gamePrepared) PrepareGame(); RestartPacketGeneration: if (networkLag > 50) Die("Lag > 50. You are not seriously playing, are you?\n"); if (laggy < 0) if (++laggy == 0) laggy = LAGGY_START; if (laggy > 0) laggy--; if (laggy == 0 && networkLag > 0) { laggy = -networkLag-2; networkLag--; DebugLevel(1, "Network lag decreased to %d\n", networkLag); // skip sending for one networkFrame } else { GenerateLocalPacket(networkFrame + networkLag); if (strlen(config.server)) { SendPackets(networkFrame + networkLag, localPlayer); } } if (laggy > LAGGY_MAX) { laggy = -networkLag-2; networkLag++; DebugLevel(1, "Network lag increased to %d\n", networkLag); // send one additional packet (FIXME: goto %-) goto RestartPacketGeneration; } if (strlen(config.replay)) { ReceivePacketsFromLogfile(networkFrame); if (paused) { DebugLevel(2, "Full game checksum is: %d\n", FullChecksum()); DebugLevel(2, "Note, NOT parsing new data now.\n"); return; //special case } } else if (strlen(config.server)) { ReceivePackets(networkFrame); } ParseNewData(networkFrame); //printf("Full checksum for networkFrame %d: %d\n", networkFrame, FullChecksum()); networkFrame++; return; }