/* Emacs style mode select -*- C++ -*- *----------------------------------------------------------------------------- * * * PrBoom: a Doom port merged with LxDoom and LSDLDoom * based on BOOM, a modified and improved DOOM engine * Copyright (C) 1999 by * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman * Copyright (C) 1999-2004 by * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze * Copyright 2005, 2006 by * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * DESCRIPTION: * Network game server code * New for LxDoom, but drawing ideas and code fragments from the * earlier net code *----------------------------------------------------------------------------- */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include // Vladimir #include #ifdef USE_SDL_NET // Vladimir #include "SDL.h" #endif #include "doomtype.h" #include "protocol.h" #include "i_network.h" #ifndef PRBOOM_SERVER #include "m_fixed.h" #endif #include "i_system.h" #include "m_swap.h" #ifndef HAVE_NET int server_main(void) { fprintf(stderr, PACKAGE "-server: You must compile with networking enabled!\n"); exit(1); return 1; } #else #ifndef HAVE_GETOPT /* The following code for getopt is from the libc-source of FreeBSD, * it might be changed a little bit. * Florian Schulze (florian.schulze@gmx.net) */ /* * Copyright (c) 1987, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if defined(LIBC_SCCS) && !defined(lint) #if 0 static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; #endif static const char rcsid[] = "$FreeBSD$"; #endif /* LIBC_SCCS and not lint */ int opterr = 1, /* if error message should be printed */ optind = 1, /* index into parent argv vector */ optopt, /* character checked for validity */ optreset; /* reset getopt */ char *optarg; /* argument associated with option */ #define BADCH (int)'?' #define BADARG (int)':' #define EMSG "" char *__progname="prboom_server"; /* * getopt -- * Parse argc/argv argument vector. */ int getopt(nargc, nargv, ostr) int nargc; char * const *nargv; const char *ostr; { extern char *__progname; static char *place = EMSG; /* option letter processing */ char *oli; /* option letter list index */ int ret; if (optreset || !*place) { /* update scanning pointer */ optreset = 0; if (optind >= nargc || *(place = nargv[optind]) != '-') { place = EMSG; return (-1); } if (place[1] && *++place == '-') { /* found "--" */ ++optind; place = EMSG; return (-1); } } /* option letter okay? */ if ((optopt = (int)*place++) == (int)':' || !(oli = strchr(ostr, optopt))) { /* * if the user didn't specify '-' as an option, * assume it means -1. */ if (optopt == (int)'-') return (-1); if (!*place) ++optind; if (opterr && *ostr != ':') (void)fprintf(stderr, "%s: illegal option -- %c\n", __progname, optopt); return (BADCH); } if (*++oli != ':') { /* don't need argument */ optarg = NULL; if (!*place) ++optind; } else { /* need an argument */ if (*place) /* no white space */ optarg = place; else if (nargc <= ++optind) { /* no arg */ place = EMSG; if (*ostr == ':') ret = BADARG; else ret = BADCH; if (opterr) (void)fprintf(stderr, "%s: option requires an argument -- %c\n", __progname, optopt); return (ret); } else /* white space */ optarg = nargv[optind]; place = EMSG; ++optind; } return (optopt); /* dump back option letter */ } #else #include #endif #define MAXPLAYERS 4 #define BACKUPTICS 12 // Dummies to forfill l_udp.c unused client stuff // Vladimir int M_CheckParm(const char* p) { p = NULL; return 1; } int myargc; char** myargv; /* vladimir void NORETURN I_Error(const char *error, ...) // killough 3/20/98: add const { va_list argptr; va_start(argptr,error); vfprintf(stderr,error,argptr); va_end(argptr); exit(-1); } */ int playerjoingame[MAXPLAYERS], playerleftgame[MAXPLAYERS]; UDP_CHANNEL remoteaddr[MAXPLAYERS]; enum { pc_unused, pc_connected, pc_ready, pc_confirmedready, pc_playing, pc_quit } playerstate[MAXPLAYERS]; int displaycounter; boolean n_players_in_state(int n, int ps) { int i,j; for (i=j=0;itic); } void read_config_file(FILE* fp, struct setup_packet_s* sp) { byte* gameopt = sp->game_options; while (!feof(fp)) { char def[80]; char strparm[100]; if (fscanf (fp, "%79s %99[^\n]\n", def, strparm) == 2) { int v = atoi(strparm); if (!strcmp(def,"default_skill")) { if (verbose) printf("config file sets default_skill to %d\n",v); sp->skill = v-1; } else if (!strcmp(def,"default_compatibility_level")) { if (verbose) printf("config file sets compatibility_level to %d\n",v); if (v == -1) v = MAX_COMPATIBILITY_LEVEL-1; //e6y: -1 => maxcompat sp->complevel = v; } else { int i; for (i=0; i= MAXPLAYERS); } int server_main(int argc, char** argv) { #ifndef USE_SDL_NET int localport = 5030; #else Uint16 localport = 5030; #endif int numplayers = 2, xtratics = 0, ticdup = 1; int exectics = 0; // gametics completed struct setup_packet_s setupinfo = { 2, 0, 1, 1, 1, 0, best_compatibility, 0, 0}; char**wadname = NULL; char**wadget = NULL; int numwads = 0; { int opt; byte *gameopt = setupinfo.game_options; memcpy(gameopt, &def_game_options, sizeof (setupinfo.game_options)); while ((opt = getopt(argc, argv, "c:t:x:p:e:l:adrfns:N:vw:")) != EOF) switch (opt) { case 'c': { FILE *cf = fopen(optarg,"r"); if (!cf) { perror("fopen"); return -1; } read_config_file(cf,&setupinfo); fclose(cf); } break; case 't': if (optarg) ticdup = atoi(optarg); break; case 'x': if (optarg) xtratics = atoi(optarg); break; case 'p': if (optarg) localport = atoi(optarg); break; case 'e': if (optarg) setupinfo.episode = atoi(optarg); break; case 'l': if (optarg) setupinfo.level = atoi(optarg); break; case 'a': setupinfo.deathmatch = 2; break; case 'd': setupinfo.deathmatch = 1; break; case 'r': setupinfo.game_options[6] = 1; break; case 'f': setupinfo.game_options[7] = 1; break; case 'n': setupinfo.game_options[8] = 1; break; case 's': if (optarg) setupinfo.skill = atoi(optarg)-1; break; case 'N': if (optarg) setupinfo.players = numplayers = atoi(optarg); break; case 'v': verbose++; break; case 'w': if (optarg) { char *p; wadname = realloc(wadname, ++numwads * sizeof *wadname); wadget = realloc(wadget , numwads * sizeof *wadget ); wadname[numwads-1] = strdup(optarg); if ((p = strchr(wadname[numwads-1], ','))) { *p++ = 0; wadget[numwads-1] = p; } else wadget[numwads-1] = NULL; } break; } } setupinfo.ticdup = ticdup; setupinfo.extratic = xtratics; { /* Random number seed * Mirrors the corresponding code in G_ReadOptions */ int rngseed = (int)time(NULL); setupinfo.game_options[13] = rngseed & 0xff; rngseed >>= 8; setupinfo.game_options[12] = rngseed & 0xff; rngseed >>= 8; setupinfo.game_options[11] = rngseed & 0xff; rngseed >>= 8; setupinfo.game_options[10] = rngseed & 0xff; } I_InitSockets(localport); printf("Listening on port %d, waiting for %d players\n", localport, numplayers); { // no players initially int i; for (i=0; i2) printf("Received packet:"); switch (packet->type) { case PKT_INIT: if (!ingame) { { int n; struct setup_packet_s *sinfo = (void*)(packet+1); /* Find player number and add to the game */ n = *(short*)(packet+1); if (badplayer(n) || playerstate[n] != pc_unused) for (n=0; nyourplayer = n; sinfo->numwads = numwads; for (i=0; iwadnames + extrabytes, wadname[i]); extrabytes += strlen(wadname[i]) + 1; } I_SendPacketTo(packet, sizeof *packet + sizeof setupinfo + extrabytes, remoteaddr+n); I_uSleep(10000); I_SendPacketTo(packet, sizeof *packet + sizeof setupinfo + extrabytes, remoteaddr+n); } } } break; case PKT_GO: if (!ingame) { int from = *(byte*)(packet+1); if (badplayer(from) || playerstate[from] == pc_unused) break; if (confirming) { if (playerstate[from] != pc_confirmedready) curplayers++; playerstate[from] = pc_confirmedready; } else playerstate[from] = pc_ready; } break; case PKT_TICC: { byte tics = *(byte*)(packet+1); int from = *(((byte*)(packet+1))+1); if (badplayer(from)) break; if (verbose>2) printf("tics %ld - %ld from %d\n", ptic(packet), ptic(packet) + tics - 1, from); if (ptic(packet) > remoteticfrom[from]) { // Missed tics, so request a resend packet_set(packet, PKT_RETRANS, remoteticfrom[from]); I_SendPacketTo(packet, sizeof *packet, remoteaddr+from); } else { ticcmd_t *newtic = (void*)(((byte*)(packet+1))+2); if (ptic(packet) + tics < remoteticfrom[from]) break; // Won't help remoteticfrom[from] = ptic(packet); while (tics--) netcmds[from][remoteticfrom[from]++%BACKUPTICS] = *newtic++; } } break; case PKT_RETRANS: { int from = *(byte*)(packet+1); if (badplayer(from)) break; if (verbose>2) printf("%d requests resend from %ld\n", from, ptic(packet)); remoteticto[from] = ptic(packet); } break; case PKT_QUIT: { int from = *(byte*)(packet+1); if (badplayer(from)) break; if (!ingame && playerstate[from] != pc_unused) { // If we already got a PKT_GO, we have to remove this player frmo the count of ready players. And we then flag this player slot as vacant. printf("player %d pulls out\n", from); if (playerstate[from] == pc_confirmedready) curplayers--; playerstate[from] = pc_unused; } else if (playerleftgame[from] == INT_MAX) { // In the game playerleftgame[from] = ptic(packet); --curplayers; if (verbose) printf("%d quits at %ld (%d left)\n", from, ptic(packet),curplayers); if (ingame && !curplayers) exit(0); // All players have exited } } // Fall through and broadcast it case PKT_EXTRA: BroadcastPacket(packet, len); if (packet->type == PKT_EXTRA) { if (verbose>2) printf("misc from %d\n", *(((byte*)(packet+1))+1)); } break; case PKT_WAD: { int i; int from = *(byte*)(packet+1); char *name = 1 + (char*)(packet+1); size_t size = sizeof(packet_header_t); packet_header_t *reply; if (badplayer(from) || playerstate[from] != pc_unused) break; if (verbose) printf("Request for %s ", name); for (i=0; itype); break; } } free(packet); if (!ingame && n_players_in_state(numplayers,pc_confirmedready)) { int i; packet_header_t gopacket; packet = &gopacket; ingame=true; printf("All players joined, beginning game.\n"); for (i=0; i1) printf("%d new tics can be run\n", lowtic - exectics); if (lowtic > exectics) exectics = lowtic; // count exec'ed tics // Now send all tics up to lowtic for (i=0; i1) printf("sending %d tics to %d\n", tics, i); while (tics--) { int j, playersthistic = 0; byte *q = p++; for (j=0; j remoteticto[i])) { *p++ = j; memcpy(p, &netcmds[j][remoteticto[i]%BACKUPTICS], sizeof(ticcmd_t)); p += sizeof(ticcmd_t); playersthistic++; } *q = playersthistic; remoteticto[i]++; } I_SendPacketTo(packet, p - ((byte*)packet), remoteaddr+i); free(packet); } { if (remoteticfrom[i] == remoteticto[i]) { backoffcounter[i] = 0; } else if (remoteticfrom[i] > remoteticto[i]+1) { if ((backoffcounter[i] += remoteticfrom[i] - remoteticto[i] - 1) > 35) { packet_header_t *packet = malloc(sizeof(packet_header_t)); packet_set(packet, PKT_BACKOFF, remoteticto[i]); I_SendPacketTo(packet,sizeof *packet,remoteaddr+i); backoffcounter[i] = 0; if (verbose) printf("telling client %d to back off\n",i); free(packet); } } } } } if (!((ingame ? 0xff : 0xf) & displaycounter++)) { int i; fprintf(stderr,"Player states: ["); for (i=0;i