/* $Id: network.c,v 1.62 2004/02/11 21:46:48 stpohle Exp $ */ /* network routines. */ #include "bomberclone.h" #include "network.h" #include "chat.h" #include "packets.h" #include "ogcache-client.h" #include "menu.h" int network_server_port (char *server, char *host, int hostlen, char *port, int portlen) { char *pos, *pos2; if (host == NULL) return -1; pos2 = pos = strchr (server, ':'); if (pos != NULL) while (pos2 != NULL) { pos = pos2; pos2 = strchr (pos + 1, ':'); } if (pos != NULL) { // : für Portangabe gefunden if (pos - server < hostlen) { strncpy (host, server, pos - server); host[pos - server] = 0; if (pos[1] == 0) sprintf (port, "11000"); else strcpy (port, pos + 1); } else { return -1; } } else { // Portangabe wurde nicht gefunden und wird auf 0 gesetzt strncpy (host, server, hostlen); sprintf (port, "11000"); } return 0; }; /* try to work better with the network packet option */ void net_dyn_pkgoption () { int p; _net_pkgopt *npkg; for (p = 0; p < MAX_PLAYERS; p++) if (PS_IS_netplayer (players[p].state) && !PS_IS_aiplayer(players[p].state)) { npkg = &players[p].net.pkgopt; if (npkg->to_2sec > DYN_PKG_MAX_MISSING) { if (npkg->send_set < 10) npkg->send_set++; npkg->to_2sec = 0; npkg->to_timestamp = timestamp; } if ((timestamp - npkg->to_timestamp > 2000) && npkg->to_2sec <= DYN_PKG_MIN_MISSING) { if (npkg->send_set > PKG_SENDSETOPT) npkg->send_set--; npkg->to_2sec = 0; npkg->to_timestamp = timestamp; } } }; /* setup everything for the network loop */ int network_init () { char host[LEN_SERVERNAME]; char port[LEN_PORT]; /* we need it for the windows winsock */ #ifdef _WIN32 WSADATA wsaData; if (WSAStartup (MAKEWORD (1, 1), &wsaData) != 0) { d_printf ("WSAStartup failed.\n"); exit (1); } #endif if (bman.net_ai_family == PF_INET) sprintf (host, "IPv4"); #ifndef _WIN32 else if (bman.net_ai_family == PF_INET6) sprintf (host, "IPv6"); #endif else sprintf (host, "IPv (unknown)"); d_printf ("Network Init with %s.\n", host); bman.sock = -1; timestamp = SDL_GetTicks (); // start the udp server bman.sock = udp_server (bman.port, bman.net_ai_family); if (bman.sock < 0) { #ifdef _WIN32 WSACleanup (); #endif return -1; } if (bman.notifygamemaster) { network_server_port (bman.ogcserver, host, LEN_SERVERNAME, port, LEN_PORT); if (ogc_init (bman.ogc_port, host, port, "BomberClone", bman.net_ai_family) == 0) bman.notifygamemaster = 0; } // we have got our socket.. so now allocate the memory for the resend_cache resend_cache.data = (char *) malloc (PKG_RESENDCACHE_SIZE); resend_cache.fill = 0; return 0; }; /* shutdown the network part */ void network_shutdown () { int i; d_printf ("network_shutdown\n"); if (GT_MP_PTPM) { d_printf ("Server Quit Send information\n"); for (i = 0; i < MAX_PLAYERS; i++) if (i != bman.p_servnr && PS_IS_netplayer (players[i].state) && !PS_IS_aiplayer (players[i].state)) send_quit (&players[i].net.addr, NULL, NULL); } else if (players[bman.p_servnr].net.addr.host[0] != 0) send_quit (&players[bman.p_servnr].net.addr, NULL, NULL); if (bman.notifygamemaster) { ogc_sendgamequit (bman.sock); ogc_shutdown (); } udp_close (bman.sock); if (resend_cache.data != NULL) free (resend_cache.data); resend_cache.data = NULL; resend_cache.fill = -1; bman.p_nr = -1; bman.sock = -1; #ifdef _WIN32 WSACleanup (); #endif }; int net_check_timeout (int pl_nr) { int timeout = UDP_TIMEOUT; if ((players[pl_nr].state & (PSF_net + PSF_used + PSF_ai)) == (PSF_used + PSF_net) && timestamp - players[pl_nr].net.timestamp > timeout && players[pl_nr].net.pingreq != players[pl_nr].net.pingack) { d_printf ("net_check_timeout pl_nr=%d, ack=%d, req=%d, timediff=%d\n", pl_nr, players[pl_nr].net.pingack, players[pl_nr].net.pingreq, timestamp - players[pl_nr].net.timestamp); players[pl_nr].net.timestamp = timestamp; players[pl_nr].net.pingack = players[pl_nr].net.pingreq; send_ping (&players[pl_nr].net.addr, players[pl_nr].net.pingack + 100, PKG_pingreq); } if ((players[pl_nr].state & (PSF_net + PSF_used + PSF_ai)) == (PSF_used + PSF_net) && timestamp - players[pl_nr].net.timestamp > timeout && players[pl_nr].net.pingreq == players[pl_nr].net.pingack) { d_printf ("net_check_timeout pl_nr=%d, ack=%d, req=%d, timediff=%d\n", pl_nr, players[pl_nr].net.pingack, players[pl_nr].net.pingreq, timestamp - players[pl_nr].net.timestamp); return 1; } return 0; }; /* Read data from the network and work with it */ int network_loop () { char data[MAX_UDPDATA]; struct pkg *packet = (struct pkg *) data; int inlen, i; _net_addr addr; if (bman.state != GS_running && bman.state != GS_ready) timestamp = SDL_GetTicks (); /* as long as we get any new data, work with them */ inlen = udp_get (bman.sock, data, MAX_UDPDATA, &addr.sAddr, bman.net_ai_family); addr.port[0] = addr.host[0] = 0; if (inlen > 0) dns_filladdr (addr.host, LEN_SERVERNAME, addr.port, LEN_PORT, bman.net_ai_family, &addr.sAddr); while (inlen > 0) { do_pkg (packet, &addr); // printf ("Network : inlen (%d) typ (%d) Size (%d)\n", inlen, packet->typ, pkglen); inlen = udp_get (bman.sock, data, MAX_UDPDATA, &addr.sAddr, bman.net_ai_family); addr.port[0] = addr.host[0] = 0; if (inlen > 0) dns_filladdr (addr.host, LEN_SERVERNAME, addr.port, LEN_PORT, bman.net_ai_family, &addr.sAddr); } /* check here for old connections who aren't answering */ if (bman.state == GS_wait || bman.state == GS_ready || bman.state == GS_running) { if (GT_MP_PTPS) { if (net_check_timeout (bman.p_servnr)) { d_printf ("Server Timed Out\n"); bman.state = GS_startup; } } else if (GT_MP_PTPM) { for (i = 1; i < MAX_PLAYERS; i++) if (i != bman.p_nr && net_check_timeout (i)) { d_printf ("Player %d Timed Out\n", i); net_delplayer (i); } } } /* resend_cache.... */ rscache_loop (); /* dynamic calibration of the network traffic option */ net_dyn_pkgoption (); return 0; }; /* this is needed to draw the whole uppdate of everything */ void draw_netupdatestate (char st) { char text[255]; unsigned char b; int y = 0, b1, z, zx=200, i, j, s=map.size.y+MAX_PLAYERS+GAME_MAX_TUNNELS; SDL_Rect src, dest; z=gfx.res.x-zx-30-8; for (i = 0; i < MAX_PLAYERS; i++) if (PS_IS_used (players[i].state)) { y += 50; if (st) { redraw_logo (0, y, gfx.res.x, y + 50); if (players[i].gfx_nr != -1) { dest.w = src.w = players[i].gfx->smal_size.x; dest.h = src.h = players[i].gfx->smal_size.y; src.x = players[i].gfx->smal_size.x * down; src.y = 0; dest.x = 50; dest.y = y; SDL_BlitSurface (players[i].gfx->smal_image, &src, gfx.screen, &dest); gfx_blitupdaterectadd (&dest); } dest.x = zx; dest.y = y; dest.w = menu.listimages[1][0]->w; dest.h = menu.listimages[1][0]->h; gfx_blit (menu.listimages[1][0], NULL, gfx.screen, &dest, 10000); dest.x = z+zx+4; gfx_blit (menu.listimages[1][2], NULL, gfx.screen, &dest, 10000); // draw the bottom left and right of the list dest.y = y+29; gfx_blit (menu.listimages[1][8], NULL, gfx.screen, &dest, 10000); dest.x = zx; gfx_blit (menu.listimages[1][6], NULL, gfx.screen, &dest, 10000); //top & bottom for (j=4;j0) { b1=b*z/255; dest.x = zx+4; dest.y = y+4; dest.w = menu.buttonimages[2][0]->w; dest.h = menu.buttonimages[2][0]->h; gfx_blit (menu.buttonimages[2][0], NULL, gfx.screen, &dest, 10000); dest.x = zx+4+b1-menu.buttonimages[1][2]->w; if (dest.xw; dest.h = menu.buttonimages[2][2]->h; gfx_blit (menu.buttonimages[2][2], NULL, gfx.screen, &dest, 10000); if (b1>menu.buttonimages[2][0]->w+menu.buttonimages[2][2]->w) { dest.w = menu.buttonimages[2][1]->w; dest.h = menu.buttonimages[2][1]->h; for(j=menu.buttonimages[2][0]->w;jw; j+=menu.buttonimages[2][1]->w) { dest.x=j+zx+4; gfx_blit (menu.buttonimages[2][1], NULL, gfx.screen, &dest, 10000); } } } // draw old status in case of debug if (!players[i].net.net_istep) font_draw (80, y + 20, text, 0, 4); else if (debug) { redraw_logo (80, y+35, gfx.res.x-80, 15); font_draw (80, y + 35, text, 0, 4); } } gfx_blitdraw (); return; } /* used to update settings at startup */ void net_change_playerid (int pl_nr, unsigned char senddata) { int i; if (GT_MP_PTPM) { /* Send to all connected clients the update */ for (i = 1; i < MAX_PLAYERS; i++) if (!PS_IS_aiplayer (players[i].state) && players[i].net.addr.host[0] != 0) send_playerid (&players[i].net.addr, players[pl_nr].name, players[pl_nr].net.addr.host, players[pl_nr].net.addr.port, pl_nr, players[pl_nr].gfx_nr, players[pl_nr].net.flags); } else { /* Send only to the Server the update and only if in_nr == bman.in_nr */ if (pl_nr == bman.p_nr && senddata) send_playerid (&players[bman.p_servnr].net.addr, players[pl_nr].name, players[pl_nr].net.addr.host, players[pl_nr].net.addr.port, pl_nr, players[pl_nr].gfx_nr, players[pl_nr].net.flags); } player_set_gfx (&players[pl_nr], players[pl_nr].gfx_nr); }; /* sets up everything for the network game.. on servers the game field will be created and the clients will wait for the game data within the network loop */ void net_transmit_gamedata () { int done = 0, keypressed = 0, x, y, // network upload status for one step p, i, net_istep; // network init step SDL_Event event; Uint8 *keys; Uint32 downtimestamp = 0; draw_logo (); if (GT_MP_PTPM) font_draw (100, 0, "Waiting for the Clients", 1, 0); else font_draw (100, 0, "Downloading Data", 1, 0); /* prepare everything for the loop */ for (x = 0; x < MAX_PLAYERS; x++) { players[x].net.timestamp = 0; players[x].net.net_status = -1; if ((PS_IS_aiplayer (players[x].state)) || (x==bman.p_servnr)) players[x].net.net_istep = 0; else players[x].net.net_istep = 3; } y = -1; if (GT_MP_PTPM) net_istep = 0; else net_istep = 3; draw_netupdatestate (1); SDL_Flip (gfx.screen); downtimestamp = timestamp; while (!done && (bman.state == GS_update || (GT_MP_PTPS && net_istep != 0))) { /* the network thing */ network_loop (); /* if PTPM check if all players are ready */ if (GT_MP_PTPM) { if (timestamp - downtimestamp > TIME_UPDATEINFO) { downtimestamp = timestamp; net_send_updateinfo(); } for (p = 1, i = 1; p < MAX_PLAYERS; p++) if (PS_IS_playing (players[p].state) && players[p].net.net_istep != 0) i = 0; if (i == 1) { /* all players are ready */ done = 1; bman.state = GS_ready; } } /* if PTPS get all data */ if (GT_MP_PTPS) { if (net_istep == 3) { /* get tunneldata */ if ((y < GAME_MAX_TUNNELS - 1 && y == players[bman.p_nr].net.net_status) || y == -1) { y++; downtimestamp = timestamp; send_tunneldata (&players[bman.p_servnr].net.addr, y, -1, -1); } else if (y < GAME_MAX_TUNNELS && y != players[bman.p_nr].net.net_status && y >= 0 && timestamp - downtimestamp > DOWNLOAD_TIMEOUT) { /* we have got no tunnel data*/ y--; } else if (y == GAME_MAX_TUNNELS - 1 && players[bman.p_nr].net.net_status == y) { /* we have got all tunnel data */ y = -1; players[bman.p_nr].net.net_istep = --net_istep; players[bman.p_nr].net.net_status = -1; } } if (net_istep == 2) { /* get field data */ if ((y < map.size.y - 1 && y == players[bman.p_nr].net.net_status) || y == -1) { /* send field data req */ y++; downtimestamp = timestamp; send_getfield (&players[bman.p_servnr].net.addr, y); } else if (y < map.size.y && y != players[bman.p_nr].net.net_status && y >= 0 && timestamp - downtimestamp > DOWNLOAD_TIMEOUT) { /* getdata timed out - we have got no field data */ y--; } else if (y == map.size.y - 1 && players[bman.p_nr].net.net_status == y) { /* we have got all field data */ y = -1; players[bman.p_nr].net.net_istep = --net_istep; players[bman.p_nr].net.net_status = -1; } } if (net_istep == 1) { /* get player data */ if ((y < MAX_PLAYERS - 1 && y == players[bman.p_nr].net.net_status) || y == -1) { /* send player date req */ y++; downtimestamp = timestamp; send_getplayerdata (&players[bman.p_servnr].net.addr, y); } if (y < MAX_PLAYERS && y != players[bman.p_nr].net.net_status && y >= 0 && timestamp - downtimestamp > DOWNLOAD_TIMEOUT) { /* we have got no player data */ y--; } if (y == MAX_PLAYERS - 1 && players[bman.p_nr].net.net_status == y) { /* we have got all playerdata */ y = -1; players[bman.p_nr].net.net_istep = --net_istep; players[bman.p_nr].net.net_status = -1; downtimestamp = timestamp; send_playerstatus (&players[bman.p_servnr].net.addr, bman.p_nr, 0, 0); } } if (net_istep == 0 && players[bman.p_nr].net.net_status == -1 && timestamp - downtimestamp > DOWNLOAD_TIMEOUT) { /* server did not send informations back */ downtimestamp = timestamp; send_playerstatus (&players[bman.p_servnr].net.addr, bman.p_nr, 0, 0); } } /* do the grafik work */ draw_netupdatestate (0); if (SDL_PollEvent (&event) != 0) switch (event.type) { case (SDL_QUIT): bman.state = GS_quit; bman.p_nr = -1; done = 1; } keys = SDL_GetKeyState (NULL); if (keys[SDLK_ESCAPE] && event.type == SDL_KEYDOWN) { done = 1; bman.p_nr = -1; keypressed = 1; bman.state = GS_startup; } if (event.type == SDL_KEYUP) keypressed = 0; timestamp = SDL_GetTicks (); // needed for time sync. SDL_Delay (1); // we don't need here anything better /* player is only watching so just go after we have got everything go to show the field */ if (GT_MP_PTPS && bman.state == GS_update && net_istep == 0 && players[bman.p_nr].gfx_nr == -1) { done = 1; bman.state = GS_running; } } }; void net_game_send_player (int p_nr) { int p; if (GT_MP_PTPM) { for (p = 0; p < MAX_PLAYERS; p++) if (PS_IS_netplayer (players[p].state) && p != bman.p_nr) send_playerdata (&players[p].net.addr, p_nr, &players[p_nr]); } else if (p_nr == bman.p_nr) { for (p = 0; p < MAX_PLAYERS; p++) if (PS_IS_netplayer (players[p].state) && p != bman.p_nr && NET_CANSEND(p)) send_playerdata (&players[p].net.addr, p_nr, &players[p_nr]); } }; void net_game_send_playermove (int p_nr, int mustsend) { int p; _player *pl; for (p = 0; p < MAX_PLAYERS; p++) if (!PS_IS_aiplayer (players[p].state) && PS_IS_netplayer (players[p].state) && p != bman.p_nr) { pl = &players[p_nr]; pl->net.pkgopt.send_to--; if ((pl->net.pkgopt.send_to <= 0 || mustsend) && NET_CANSEND(p)) send_playermove (&players[p].net.addr, p_nr, pl); /* network packet send control */ if (pl->net.pkgopt.send_to <= 0 || pl->net.pkgopt.send_to > pl->net.pkgopt.send_set) pl->net.pkgopt.send_to = pl->net.pkgopt.send_set; } }; void net_game_send_bomb (int p, int b) { int pl; /* check if we are slave and send something else as dropping a bomb */ if (GT_MP_PTPS && players[p].bombs[b].state != BS_ticking && players[p].bombs[b].state != BS_trigger) return; d_printf ("Send BombData %d, %d\n", p, b); if (p < 0 || p >= MAX_PLAYERS || b < 0 || b >= MAX_BOMBS) return; for (pl = 0; pl < MAX_PLAYERS; pl++) if (!PS_IS_aiplayer (players[pl].state) && PS_IS_netplayer (players[pl].state) && pl != bman.p_nr && NET_CANSEND(pl)) send_bombdata (&players[pl].net.addr, p, b, &players[p].bombs[b]); }; void net_game_send_field (int x, int y) { int pl; d_printf ("Send FieldData %d, %d\n", x, y); if (x < 0 || x >= MAX_FIELDSIZE_X || y < 0 || y >= MAX_FIELDSIZE_Y) return; for (pl = 0; pl < MAX_PLAYERS; pl++) if (!PS_IS_aiplayer (players[pl].state) && PS_IS_netplayer (players[pl].state) && pl != bman.p_nr && NET_CANSEND(pl)) send_field (&players[pl].net.addr, x, y, &map.field[x][y]); }; void net_delplayer (int pl_nr) { char host[LEN_SERVERNAME]; char port[LEN_PORT]; int i; d_printf ("net_delplayer (%d)\n", pl_nr); bman.updatestatusbar = 1; // force an update if (pl_nr == bman.p_nr) { /* we're not wanted */ network_shutdown (); bman.state = GS_startup; } else { strncpy (host, players[pl_nr].net.addr.host, LEN_SERVERNAME); strncpy (port, players[pl_nr].net.addr.port, LEN_PORT); players[pl_nr].state &= (0xFF - (PSF_used + PSF_net + PSF_alife)); /* delete player flags */ players[pl_nr].net.net_istep = 0; // needed for disconnect during the update bman.players_nr_s--; players[pl_nr].gfx_nr = -1; if (GT_MP_PTPM && (GS_WAITRUNNING || bman.state == GS_update)) { for (i = 1; i < MAX_PLAYERS; i++) if (!PS_IS_aiplayer (players[i].state) && PS_IS_netplayer (players[i].state)) send_quit (&players[i].net.addr, host, port); if (bman.notifygamemaster) send_ogc_update (); bman.updatestatusbar=1; } } if (GT_MP_PTPS && pl_nr == bman.p_servnr) /* server quit */ bman.state = GS_startup; if (GT_MP_PTPM && bman.notifygamemaster) send_ogc_update (); d_playerdetail (" Player Left ... Playerlist\n"); inpkg_delplayer (pl_nr); }; void net_game_fillsockaddr () { /* Update all sockaddr before the game starts */ int i; for (i = 0; i < MAX_PLAYERS; i++) if (!PS_IS_aiplayer (players[i].state) && players[i].net.addr.host[0] != 0 && players[i].net.addr.host[0] != 0) dns_filladdr (players[i].net.addr.host, LEN_SERVERNAME, players[i].net.addr.port, LEN_PORT, bman.net_ai_family, &players[i].net.addr.sAddr); }; void net_send_servermode () { int i; for (i = 0; i < MAX_PLAYERS; i++) if (!PS_IS_aiplayer (players[i].state) && PS_IS_netplayer (players[i].state) && i != bman.p_nr && NET_CANSEND(i)) send_servermode (&players[i].net.addr, i); if (bman.notifygamemaster && GT_MP_PTPM) send_ogc_update (); }; /* sends to everyone an up to date playerlist*/ void net_send_players () { int i, j; for (j = 0; j < MAX_PLAYERS; j++) if (!PS_IS_aiplayer (players[j].state) && PS_IS_netplayer (players[j].state) && j != bman.p_nr) for (i = 0; i < MAX_PLAYERS; i++) send_playerid (&players[j].net.addr, players[i].name, players[i].net.addr.host, players[i].net.addr.port, i, players[i].gfx_nr, players[i].net.flags); }; void net_send_chat (char *text, signed char notigamesrv) { int i; for (i = 0; i < MAX_PLAYERS; i++) if ((!PS_IS_aiplayer (players[i].state)) && PS_IS_netplayer (players[i].state) && i != bman.p_nr && NET_CANSEND(i)) send_chat (&players[i].net.addr, text); }; void net_game_send_ill (int p_nr) { int i; d_printf ("net_game_send_ill (%d)\n", p_nr); for (i = 0; i < MAX_PLAYERS; i++) if ((!PS_IS_aiplayer (players[i].state)) && PS_IS_netplayer (players[i].state) && i != bman.p_nr && NET_CANSEND(i)) send_ill (&players[i].net.addr, p_nr, &players[p_nr]); }; /* send to all players the drop item list */ void net_game_send_dropitems (int pl_nr, _flyingitem **fiptr, int cnt) { int i; d_printf ("net_game_send_dropitems (%d): %d items droppped\n", pl_nr, cnt); for (i = 0; i < MAX_PLAYERS; i++) if ((!PS_IS_aiplayer (players[i].state)) && PS_IS_netplayer (players[i].state) && i != bman.p_nr && NET_CANSEND(i)) send_dropitems (&players[i].net.addr, pl_nr, fiptr, cnt); }; /* this routine will set up some things for the network game after this the data should be transfered to the other clients. */ void net_new_game () { int p; /* set all multiplayer depending datas */ bman.players_nr = 0; bman.players_nr_s = 0; for (p = 0; p < MAX_PLAYERS; p++) { if (PS_IS_used (players[p].state)) bman.players_nr_s++; else players[p].state = 0; } if (bman.p_nr != -1) players[bman.p_nr].state &= (0xFF - PSF_net); // we are the local player bman.last_ex_nr = 1; }; /* send special use elements into the network, to make sure nothing bad happens with explosions we send the ex_nr number too */ void net_game_send_special (int pl_nr, int ex_nr, int type) { int pl; d_printf ("Send Special Data %d\n", pl_nr); if (pl_nr < 0 || pl_nr >= MAX_PLAYERS) return; for (pl = 0; pl < MAX_PLAYERS; pl++) if (!PS_IS_aiplayer (players[pl].state) && PS_IS_netplayer (players[pl].state) && pl != pl_nr && NET_CANSEND(pl)) send_special (&players[pl].net.addr, pl_nr, type, ex_nr); }; /* Send update informations to all clients */ void net_send_updateinfo () { int i; if (GT_MP_PTPS) return; d_printf ("Send Updateinfo\n"); for (i = 0; i < MAX_PLAYERS; i++) if (!PS_IS_aiplayer (players[i].state) && PS_IS_netplayer (players[i].state) && i != bman.p_nr && NET_CANSEND(i)) send_updateinfo (&players[i].net.addr); }; /* Send mapinformations to all clients */ void net_send_mapinfo () { int i; if (GT_MP_PTPS) return; d_printf ("Send Mapinfo\n"); for (i = 0; i < MAX_PLAYERS; i++) if (!PS_IS_aiplayer (players[i].state) && PS_IS_netplayer (players[i].state) && i != bman.p_nr && NET_CANSEND(i)) send_mapinfo (&players[i].net.addr); }; /* send an update about the game to all clients */ void send_ogc_update () { int i, j; char status[10]; if (!bman.notifygamemaster || GT_MP_PTPS) return; for (j = 0, i = 0; i < bman.maxplayer; i++) if (PS_IS_used (players[i].state)) j++; switch (bman.state) { case (GS_running): sprintf (status, "running"); break; case (GS_ready): sprintf (status, "ready"); break; case (GS_wait): sprintf (status, "wait"); break; case (GS_update): sprintf (status, "update"); break; default: sprintf (status, "error"); break; } ogc_sendgamestatus (bman.sock, "BomberClone", VERSION, bman.gamename, j, bman.maxplayer, status); }; /* send the respawn data to all clients and with the new position of the player */ void net_game_send_respawn (int pl_nr) { int pl; d_printf ("Send Respawn Data %d New Position (%d,%d)\n", pl_nr); if (pl_nr < 0 || pl_nr >= MAX_PLAYERS) return; for (pl = 0; pl < MAX_PLAYERS; pl++) if (!PS_IS_aiplayer (players[pl].state) && PS_IS_netplayer (players[pl].state) && pl != pl_nr && NET_CANSEND(pl)) send_respawn (&players[pl].net.addr, pl_nr); };