shithub: choc

Download patch

ref: 2344223e648ca693945fe014bb3162c272bf27ec
parent: 031c8ff2d2dcb1a94d730d28555989722aeffdea
author: Simon Howard <[email protected]>
date: Fri Oct 14 14:14:34 EDT 2011

Convert Hexen to use common main loop code. Working multiplayer!

Subversion-branch: /branches/v2-branch
Subversion-revision: 2423

--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -51,6 +51,7 @@
                      doomfeatures.h        \
                      doomtype.h            \
 d_iwad.c             d_iwad.h              \
+d_loop.c             d_loop.h              \
 d_mode.c             d_mode.h              \
                      d_ticcmd.h            \
 deh_str.c            deh_str.h             \
@@ -127,7 +128,8 @@
 SOURCE_FILES = $(COMMON_SOURCE_FILES)              \
                $(GAME_SOURCE_FILES)                \
                $(FEATURE_WAD_MERGE_SOURCE_FILES)   \
-               $(FEATURE_SOUND_SOURCE_FILES)
+               $(FEATURE_SOUND_SOURCE_FILES)       \
+               $(FEATURE_MULTIPLAYER_SOURCE_FILES)
 
 SOURCE_FILES_WITH_DEH = $(SOURCE_FILES)                    \
                         $(FEATURE_DEHACKED_SOURCE_FILES)
@@ -143,25 +145,17 @@
                @SDLNET_LIBS@
 
 if HAVE_WINDRES
-@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc \
-               d_loop.c d_loop.h \
-               $(FEATURE_MULTIPLAYER_SOURCE_FILES)
+@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 else
-@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH) \
-               d_loop.c d_loop.h \
-               $(FEATURE_MULTIPLAYER_SOURCE_FILES)
+@PROGRAM_PREFIX@doom_SOURCES=$(SOURCE_FILES_WITH_DEH)
 endif
 
 @PROGRAM_PREFIX@doom_LDADD = doom/libdoom.a $(EXTRA_LIBS)
 
 if HAVE_WINDRES
-@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc \
-               d_loop.c d_loop.h \
-               $(FEATURE_MULTIPLAYER_SOURCE_FILES)
+@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 else
-@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH) \
-               d_loop.c d_loop.h \
-               $(FEATURE_MULTIPLAYER_SOURCE_FILES)
+@PROGRAM_PREFIX@heretic_SOURCES=$(SOURCE_FILES_WITH_DEH)
 endif
 
 @PROGRAM_PREFIX@heretic_LDADD = heretic/libheretic.a $(EXTRA_LIBS)
@@ -175,13 +169,9 @@
 @PROGRAM_PREFIX@hexen_LDADD = hexen/libhexen.a $(EXTRA_LIBS)
 
 if HAVE_WINDRES
-@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc \
-               d_loop.c d_loop.h \
-               $(FEATURE_MULTIPLAYER_SOURCE_FILES)
+@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH) resource.rc
 else
-@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH) \
-               d_loop.c d_loop.h \
-               $(FEATURE_MULTIPLAYER_SOURCE_FILES)
+@PROGRAM_PREFIX@strife_SOURCES=$(SOURCE_FILES_WITH_DEH)
 endif
 
 @PROGRAM_PREFIX@strife_LDADD = strife/libstrife.a $(EXTRA_LIBS)
--- a/src/hexen/d_net.c
+++ b/src/hexen/d_net.c
@@ -2,8 +2,7 @@
 //-----------------------------------------------------------------------------
 //
 // Copyright(C) 1993-1996 Id Software, Inc.
-// Copyright(C) 1993-2008 Raven Software
-// Copyright(C) 2008 Simon Howard
+// Copyright(C) 2005 Simon Howard
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -20,968 +19,259 @@
 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 // 02111-1307, USA.
 //
+// DESCRIPTION:
+//	DOOM Network game communication and protocol,
+//	all OS independend parts.
+//
 //-----------------------------------------------------------------------------
 
+#include <stdlib.h>
 
-#include "h2def.h"
-#include "s_sound.h"
-#include "doomkeys.h"
-#include "i_video.h"
+#include "doomfeatures.h"
+
+#include "m_argv.h"
 #include "i_system.h"
 #include "i_timer.h"
-#include "m_argv.h"
-#include "p_local.h"
-#include <stdlib.h>             // for atoi()
+#include "i_video.h"
+#include "h2def.h"
 
-#define NCMD_EXIT               0x80000000
-#define NCMD_RETRANSMIT 0x40000000
-#define NCMD_SETUP              0x20000000
-#define NCMD_KILL               0x10000000      // kill game
-#define NCMD_CHECKSUM   0x0fffffff
+#include "deh_main.h"
 
+#include "d_loop.h"
 
-doomcom_t *doomcom;
-doomdata_t *netbuffer;          // points inside doomcom
+ticcmd_t *netcmds;
 
+extern void H2_DoAdvanceDemo(void);
+extern void H2_ProcessEvents(void);
+extern void G_BuildTiccmd(ticcmd_t *cmd, int maketic);
+extern boolean G_CheckDemoStatus(void);
 
-/*
-==============================================================================
+extern boolean demorecording;
 
-							NETWORKING
+// Called when a player leaves the game
 
-gametic is the tic about to (or currently being) run
-maketic is the tick that hasn't had control made for it yet
-nettics[] has the maketics for all players
-
-a gametic cannot be run until nettics[] > gametic for all players
-
-==============================================================================
-*/
-
-#define RESENDCOUNT     10
-#define PL_DRONE        0x80    // bit flag in doomdata->player
-
-ticcmd_t localcmds[BACKUPTICS];
-
-ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS];
-int nettics[MAXNETNODES];
-boolean nodeingame[MAXNETNODES];        // set false as nodes leave game
-boolean remoteresend[MAXNETNODES];      // set when local needs tics
-int resendto[MAXNETNODES];      // set when remote needs tics
-int resendcount[MAXNETNODES];
-
-int nodeforplayer[MAXPLAYERS];
-
-int maketic;
-int lastnettic, skiptics;
-int ticdup;
-int maxsend;                    // BACKUPTICS/(2*ticdup)-1
-
-void H2_ProcessEvents(void);
-void G_BuildTiccmd(ticcmd_t * cmd);
-void H2_DoAdvanceDemo(void);
-extern void ST_NetProgress(void);
-extern void ST_NetDone(void);
-
-boolean reboundpacket;
-doomdata_t reboundstore;
-
-
-int NetbufferSize(void)
+static void PlayerQuitGame(player_t *player)
 {
-    return (int) &(((doomdata_t *) 0)->cmds[netbuffer->numtics]);
-}
+    static char exitmsg[80];
+    unsigned int player_num;
 
-unsigned NetbufferChecksum(void)
-{
-    unsigned c;
-    int i, l;
+    player_num = player - players;
 
-    c = 0x1234567;
+    /* TODO
+    // Do this the same way as Vanilla Doom does, to allow dehacked
+    // replacements of this message
 
-#if defined(NORMALUNIX)
-    return 0;                   // byte order problems
-#endif
+    strncpy(exitmsg, DEH_String("Player 1 left the game"), sizeof(exitmsg));
+    exitmsg[sizeof(exitmsg) - 1] = '\0';
 
-    l = (NetbufferSize() - (int) &(((doomdata_t *) 0)->retransmitfrom)) / 4;
-    for (i = 0; i < l; i++)
-        c += ((unsigned *) &netbuffer->retransmitfrom)[i] * (i + 1);
-
-    return c & NCMD_CHECKSUM;
-}
-
-int ExpandTics(int low)
-{
-    int delta;
-
-    delta = low - (maketic & 0xff);
-
-    if (delta >= -64 && delta <= 64)
-        return (maketic & ~0xff) + low;
-    if (delta > 64)
-        return (maketic & ~0xff) - 256 + low;
-    if (delta < -64)
-        return (maketic & ~0xff) + 256 + low;
-
-    I_Error("ExpandTics: strange value %i at maketic %i", low, maketic);
-    return 0;
-}
-
-
-//============================================================================
-
-
-/*
-==============
-=
-= HSendPacket
-=
-==============
+    exitmsg[7] += player_num;
+    players[consoleplayer].message = exitmsg;
 */
 
-void HSendPacket(int node, int flags)
-{
-    netbuffer->checksum = NetbufferChecksum() | flags;
+    playeringame[player_num] = false;
 
-    if (!node)
-    {
-        reboundstore = *netbuffer;
-        reboundpacket = true;
-        return;
-    }
+    // TODO: check if it is sensible to do this:
 
-    if (demoplayback)
-        return;
-
-    if (!netgame)
-        I_Error("Tried to transmit to another node");
-
-    doomcom->command = CMD_SEND;
-    doomcom->remotenode = node;
-    doomcom->datalength = NetbufferSize();
-
-    if (debugfile)
+    if (demorecording) 
     {
-        int i;
-        int realretrans;
-        if (netbuffer->checksum & NCMD_RETRANSMIT)
-            realretrans = ExpandTics(netbuffer->retransmitfrom);
-        else
-            realretrans = -1;
-        fprintf(debugfile, "send (%i + %i, R %i) [%i] ",
-                ExpandTics(netbuffer->starttic), netbuffer->numtics,
-                realretrans, doomcom->datalength);
-        for (i = 0; i < doomcom->datalength; i++)
-            fprintf(debugfile, "%i ", ((byte *) netbuffer)[i]);
-        fprintf(debugfile, "\n");
+        G_CheckDemoStatus ();
     }
-
-#ifdef I_NET
-    I_NetCmd();
-#endif
 }
 
-//==========================================================================
-//
-// NET_SendFrags
-//
-//==========================================================================
-
-void NET_SendFrags(player_t * player)
+static void RunTic(ticcmd_t *cmds, boolean *ingame)
 {
-    int i;
-    int frags;
+    extern boolean advancedemo;
+    unsigned int i;
 
-    netbuffer->checksum = NetbufferChecksum();
+    // Check for player quits.
 
-    if (demoplayback)
+    for (i = 0; i < MAXPLAYERS; ++i)
     {
-        return;
-    }
-    if (!netgame)
-    {
-        I_Error("Tried to transmit to another node");
-    }
-
-    frags = 0;
-    for (i = 0; i < MAXPLAYERS; i++)
-    {
-        frags += player->frags[i];
-    }
-    doomcom->command = CMD_FRAG;
-    doomcom->remotenode = frags;
-    doomcom->datalength = NetbufferSize();
-
-#ifdef I_NET
-    I_NetCmd();
-#endif
-}
-
-/*
-==============
-=
-= HGetPacket
-=
-= Returns false if no packet is waiting
-=
-==============
-*/
-
-boolean HGetPacket(void)
-{
-    if (reboundpacket)
-    {
-        *netbuffer = reboundstore;
-        doomcom->remotenode = 0;
-        reboundpacket = false;
-        return true;
-    }
-
-    if (!netgame)
-        return false;
-    if (demoplayback)
-        return false;
-
-    doomcom->command = CMD_GET;
-#ifdef I_NET
-    I_NetCmd();
-#endif
-    if (doomcom->remotenode == -1)
-        return false;
-
-    if (doomcom->datalength != NetbufferSize())
-    {
-        if (debugfile)
-            fprintf(debugfile, "bad packet length %i\n", doomcom->datalength);
-        return false;
-    }
-
-    if (NetbufferChecksum() != (netbuffer->checksum & NCMD_CHECKSUM))
-    {
-        if (debugfile)
-            fprintf(debugfile, "bad packet checksum\n");
-        return false;
-    }
-
-    if (debugfile)
-    {
-        int realretrans;
-        int i;
-
-        if (netbuffer->checksum & NCMD_SETUP)
-            fprintf(debugfile, "setup packet\n");
-        else
+        if (playeringame[i] && !ingame[i])
         {
-            if (netbuffer->checksum & NCMD_RETRANSMIT)
-                realretrans = ExpandTics(netbuffer->retransmitfrom);
-            else
-                realretrans = -1;
-            fprintf(debugfile, "get %i = (%i + %i, R %i)[%i] ",
-                    doomcom->remotenode, ExpandTics(netbuffer->starttic),
-                    netbuffer->numtics, realretrans, doomcom->datalength);
-            for (i = 0; i < doomcom->datalength; i++)
-                fprintf(debugfile, "%i ", ((byte *) netbuffer)[i]);
-            fprintf(debugfile, "\n");
+            PlayerQuitGame(&players[i]);
         }
     }
-    return true;
-}
 
+    netcmds = cmds;
 
-/*
-===================
-=
-= GetPackets
-=
-===================
-*/
+    // check that there are players in the game.  if not, we cannot
+    // run a tic.
 
-char exitmsg[80];
+    if (advancedemo)
+        H2_DoAdvanceDemo ();
 
-void GetPackets(void)
-{
-    int netconsole;
-    int netnode;
-    ticcmd_t *src, *dest;
-    int realend;
-    int realstart;
-
-    while (HGetPacket())
-    {
-        if (netbuffer->checksum & NCMD_SETUP)
-            continue;           // extra setup packet
-
-        netconsole = netbuffer->player & ~PL_DRONE;
-        netnode = doomcom->remotenode;
-        //
-        // to save bytes, only the low byte of tic numbers are sent
-        // Figure out what the rest of the bytes are
-        //
-        realstart = ExpandTics(netbuffer->starttic);
-        realend = (realstart + netbuffer->numtics);
-
-        //
-        // check for exiting the game
-        //
-        if (netbuffer->checksum & NCMD_EXIT)
-        {
-            if (!nodeingame[netnode])
-                continue;
-            nodeingame[netnode] = false;
-            playeringame[netconsole] = false;
-            strcpy(exitmsg, "PLAYER 1 LEFT THE GAME");
-            exitmsg[7] += netconsole;
-            P_SetMessage(&players[consoleplayer], exitmsg, true);
-            S_StartSound(NULL, SFX_CHAT);
-//                      players[consoleplayer].message = exitmsg;
-//                      if (demorecording)
-//                              G_CheckDemoStatus ();
-            continue;
-        }
-
-        //
-        // check for a remote game kill
-        //
-        if (netbuffer->checksum & NCMD_KILL)
-            I_Error("Killed by network driver");
-
-        nodeforplayer[netconsole] = netnode;
-
-        //
-        // check for retransmit request
-        //
-        if (resendcount[netnode] <= 0
-            && (netbuffer->checksum & NCMD_RETRANSMIT))
-        {
-            resendto[netnode] = ExpandTics(netbuffer->retransmitfrom);
-            if (debugfile)
-                fprintf(debugfile, "retransmit from %i\n", resendto[netnode]);
-            resendcount[netnode] = RESENDCOUNT;
-        }
-        else
-            resendcount[netnode]--;
-
-        //
-        // check for out of order / duplicated packet
-        //
-        if (realend == nettics[netnode])
-            continue;
-
-        if (realend < nettics[netnode])
-        {
-            if (debugfile)
-                fprintf(debugfile, "out of order packet (%i + %i)\n",
-                        realstart, netbuffer->numtics);
-            continue;
-        }
-
-        //
-        // check for a missed packet
-        //
-        if (realstart > nettics[netnode])
-        {
-            // stop processing until the other system resends the missed tics
-            if (debugfile)
-                fprintf(debugfile, "missed tics from %i (%i - %i)\n", netnode,
-                        realstart, nettics[netnode]);
-            remoteresend[netnode] = true;
-            continue;
-        }
-
-//
-// update command store from the packet
-//
-        {
-            int start;
-
-            remoteresend[netnode] = false;
-
-            start = nettics[netnode] - realstart;
-            src = &netbuffer->cmds[start];
-
-            while (nettics[netnode] < realend)
-            {
-                dest = &netcmds[netconsole][nettics[netnode] % BACKUPTICS];
-                nettics[netnode]++;
-                *dest = *src;
-                src++;
-            }
-        }
-    }
-
+    G_Ticker ();
 }
 
-/*
-=============
-=
-= NetUpdate
-=
-= Builds ticcmds for console player
-= sends out a packet
-=============
-*/
+static loop_interface_t hexen_loop_interface = {
+    H2_ProcessEvents,
+    G_BuildTiccmd,
+    RunTic,
+    MN_Ticker
+};
 
-int gametime;
 
-void NetUpdate(void)
+// Load game settings from the specified structure and 
+// set global variables.
+
+static void LoadGameSettings(net_gamesettings_t *settings,
+                             net_connect_data_t *connect_data)
 {
-    int nowtime;
-    int newtics;
-    int i, j;
-    int realstart;
-    int gameticdiv;
+    unsigned int i;
 
-//
-// check time
-//
-    nowtime = I_GetTime() / ticdup;
-    newtics = nowtime - gametime;
-    gametime = nowtime;
+    deathmatch = settings->deathmatch;
+    ticdup = settings->ticdup;
+    startepisode = settings->episode;
+    startmap = settings->map;
+    startskill = settings->skill;
+    // TODO startloadgame = settings->loadgame;
+    nomonsters = settings->nomonsters;
+    respawnparm = settings->respawn_monsters;
 
-    if (newtics <= 0)           // nothing new to update
-        goto listen;
-
-    if (skiptics <= newtics)
+    if (!connect_data->drone)
     {
-        newtics -= skiptics;
-        skiptics = 0;
+        consoleplayer = settings->consoleplayer;
     }
     else
     {
-        skiptics -= newtics;
-        newtics = 0;
+        consoleplayer = 0;
     }
 
-
-    netbuffer->player = consoleplayer;
-
-//
-// build new ticcmds for console player
-//
-    gameticdiv = gametic / ticdup;
-    for (i = 0; i < newtics; i++)
+    for (i=0; i<MAXPLAYERS; ++i)
     {
-        I_StartTic();
-        H2_ProcessEvents();
-        if (maketic - gameticdiv >= BACKUPTICS / 2 - 1)
-            break;              // can't hold any more
-//printf ("mk:%i ",maketic);
-        G_BuildTiccmd(&localcmds[maketic % BACKUPTICS]);
-        maketic++;
-    }
+        playeringame[i] = i < settings->num_players;
+        PlayerClass[i] = settings->player_classes[i];
 
-
-    if (singletics)
-        return;                 // singletic update is syncronous
-
-//
-// send the packet to the other nodes
-//
-    for (i = 0; i < doomcom->numnodes; i++)
-        if (nodeingame[i])
+        if (PlayerClass[i] >= NUMCLASSES)
         {
-            netbuffer->starttic = realstart = resendto[i];
-            netbuffer->numtics = maketic - realstart;
-            if (netbuffer->numtics > BACKUPTICS)
-                I_Error("NetUpdate: netbuffer->numtics > BACKUPTICS");
-
-            resendto[i] = maketic - doomcom->extratics;
-
-            for (j = 0; j < netbuffer->numtics; j++)
-                netbuffer->cmds[j] = localcmds[(realstart + j) % BACKUPTICS];
-
-            if (remoteresend[i])
-            {
-                netbuffer->retransmitfrom = nettics[i];
-                HSendPacket(i, NCMD_RETRANSMIT);
-            }
-            else
-            {
-                netbuffer->retransmitfrom = 0;
-                HSendPacket(i, 0);
-            }
+            PlayerClass[i] = PCLASS_FIGHTER;
         }
-
-//
-// listen for other packets
-//
-  listen:
-
-    GetPackets();
-}
-
-
-/*
-=====================
-=
-= CheckAbort
-=
-=====================
-*/
-
-void CheckAbort(void)
-{
-    event_t *ev;
-    int stoptic;
-
-    stoptic = I_GetTime() + 2;
-    while (I_GetTime() < stoptic)
-        I_StartTic();
-
-    I_StartTic();
-    for (;;)
-    {
-        ev = D_PopEvent();
-
-        if (ev == NULL)
-        {
-            break;
-        }
-
-        if (ev->type == ev_keydown && ev->data1 == KEY_ESCAPE)
-            I_Error("Network game synchronization aborted.");
     }
 }
 
-/*
-=====================
-=
-= D_ArbitrateNetStart
-=
-=====================
-*/
+// Save the game settings from global variables to the specified
+// game settings structure.
 
-void D_ArbitrateNetStart(void)
+static void SaveGameSettings(net_gamesettings_t *settings,
+                             net_connect_data_t *connect_data)
 {
     int i;
-    boolean gotinfo[MAXNETNODES];
-    boolean gotClass[MAXNETNODES];
-    // haleyjd FIXME: important somehow?
-#ifdef __WATCOMC__
-    int nextTic;
-    extern volatile int ticcount;
 
-    nextTic = ticcount + 8;
-#endif
+    // Fill in game settings structure with appropriate parameters
+    // for the new game
 
-    autostart = true;
+    settings->deathmatch = deathmatch;
+    settings->episode = startepisode;
+    settings->map = startmap;
+    settings->skill = startskill;
+    // TODO settings->loadgame = startloadgame;
+    settings->gameversion = exe_hexen_1_1;
+    settings->nomonsters = nomonsters;
+    settings->respawn_monsters = respawnparm;
+    settings->timelimit = 0;
 
-    memset(gotClass, 0, sizeof(gotClass));
-    memset(gotinfo, 0, sizeof(gotinfo));
-    gotClass[doomcom->consoleplayer] = true;
-    do
-    {
-        i = 0;
+    settings->lowres_turn = false;
 
-        CheckAbort();
-        while (HGetPacket())
-        {                       // Check for any incoming packets
-            if (netbuffer->checksum & NCMD_SETUP && netbuffer->starttic >= 64)
-            {
+    //
+    // Connect data
+    //
 
-                PlayerClass[netbuffer->player] = netbuffer->starttic & 0x3f;
-                if (!gotClass[netbuffer->player])
-                {
-                    gotClass[netbuffer->player] = true;
-                    ST_NetProgress();
-                    ST_Message("\n");
-                }
-                if (netbuffer->retransmitfrom)
-                {               // that node has received info from all other nodes
-                    gotinfo[netbuffer->player] = true;
-                }
-            }
-        }
-        // haleyjd FIXME: important somehow?
-#ifdef __WATCOMC__
-        if (ticcount <= nextTic)
-        {                       // only send packets every half second
-            continue;
-        }
-        nextTic = ticcount + 8;
-#endif
-        // Keep sending out packets containing the console class
-        for (i = 0; i < doomcom->numnodes; i++)
-        {
-            netbuffer->player = doomcom->consoleplayer;
-            netbuffer->starttic = PlayerClass[doomcom->consoleplayer] + 64;
-            netbuffer->retransmitfrom = gotinfo[doomcom->consoleplayer];
-            netbuffer->numtics = 0;
-            HSendPacket(i, NCMD_SETUP);
-        }
-        for (i = 0; i < doomcom->numnodes; i++)
-        {                       // Make sure that all nodes have sent class info
-            if (!gotClass[i])
-            {
-                ST_Message(".");
-                break;
-            }
-        }
-        if (i < doomcom->numnodes)
-        {
-            continue;
-        }
-        else
-        {                       // consoleplayer has received all player classes
-            if (gotinfo[doomcom->consoleplayer])
-            {
-                CheckAbort();
-            }
-            else
-            {
-                gotinfo[doomcom->consoleplayer] = true;
-                ST_Message("All player classes received, ready to proceed\n");
-                ST_NetDone();
-            }
-        }
-        for (i = 0; i < doomcom->numnodes; i++)
-        {                       // Make sure that all nodes are ready to proceed
-            if (!gotinfo[i])
-            {
-                break;
-            }
-        }
-    }
-    while (i < doomcom->numnodes);
+    // Game type fields:
 
-    memset(gotinfo, 0, sizeof(gotinfo));
+    connect_data->gamemode = gamemode;
+    connect_data->gamemission = gamemission;
 
-    if (doomcom->consoleplayer)
-    {                           // listen for setup info from key player
-//              ST_Message ("listening for network start info...\n");
-        while (1)
-        {
-            CheckAbort();
-            if (!HGetPacket())
-                continue;
-            if (netbuffer->checksum & NCMD_SETUP && netbuffer->starttic < 64)
-            {
-                if (netbuffer->player != HEXEN_VERSION)
-                    I_Error
-                        ("Different HEXEN versions cannot play a net game!");
-                startskill = netbuffer->retransmitfrom & 15;
-                deathmatch = (netbuffer->retransmitfrom & 0xc0) >> 6;
-                nomonsters = (netbuffer->retransmitfrom & 0x20) > 0;
-                respawnparm = (netbuffer->retransmitfrom & 0x10) > 0;
-                startmap = netbuffer->starttic & 0x3f;
-                startepisode = 1;
-                return;
-            }
-        }
-    }
-    else
-    {                           // key player, send the setup info
-//              ST_Message ("sending network start info...\n");
-        do
-        {
-            CheckAbort();
-            for (i = 0; i < doomcom->numnodes; i++)
-            {
-                netbuffer->retransmitfrom = startskill;
-                if (deathmatch)
-                    netbuffer->retransmitfrom |= (deathmatch << 6);
-                if (nomonsters)
-                    netbuffer->retransmitfrom |= 0x20;
-                if (respawnparm)
-                    netbuffer->retransmitfrom |= 0x10;
-                netbuffer->starttic = startmap & 0x3f;
-                netbuffer->player = HEXEN_VERSION;
-                netbuffer->numtics = 0;
-                HSendPacket(i, NCMD_SETUP);
-            }
+    connect_data->lowres_turn = false;
+    connect_data->drone = false;
 
-#if 1
-            for (i = 10; i && HGetPacket(); --i)
-            {
-                if ((netbuffer->player & 0x7f) < MAXNETNODES)
-                    gotinfo[netbuffer->player & 0x7f] = true;
-            }
-#else
-            while (HGetPacket())
-            {
-                gotinfo[netbuffer->player & 0x7f] = true;
-            }
-#endif
-
-            for (i = 1; i < doomcom->numnodes; i++)
-                if (!gotinfo[i])
-                    break;
-        }
-        while (i < doomcom->numnodes);
-    }
-}
-
-// Dummy version of I_InitNetwork; netgames are currently broken.
-
-void I_InitNetwork(void)
-{
+    //!
+    // @category net
+    // @arg <n>
     //
-    // single player game
+    // Specify player class: 0=fighter, 1=cleric, 2=mage, 3=pig.
     //
-    doomcom = malloc(sizeof(*doomcom));
-    memset(doomcom, 0, sizeof(*doomcom));
-    netgame = false;
-    doomcom->id = DOOMCOM_ID;
-    doomcom->numplayers = doomcom->numnodes = 1;
-    doomcom->deathmatch = false;
-    doomcom->consoleplayer = 0;
-    doomcom->ticdup = 1;
-    doomcom->extratics = 0;
-}
 
-/*
-===================
-=
-= D_CheckNetGame
-=
-= Works out player numbers among the net participants
-===================
-*/
+    i = M_CheckParmWithArgs("-class", 1);
 
-extern int viewangleoffset;
-
-void D_CheckNetGame(void)
-{
-    int i;
-    int pClass;
-
-    for (i = 0; i < MAXNETNODES; i++)
+    if (i > 0)
     {
-        nodeingame[i] = false;
-        nettics[i] = 0;
-        remoteresend[i] = false;        // set when local needs tics
-        resendto[i] = 0;        // which tic to start sending
+        connect_data->player_class = atoi(myargv[i + 1]);
     }
-
-// I_InitNetwork sets doomcom and netgame
-    I_InitNetwork();
-    if (doomcom->id != DOOMCOM_ID)
-        I_Error("Doomcom buffer invalid!");
-    netbuffer = &doomcom->data;
-    consoleplayer = displayplayer = doomcom->consoleplayer;
-    pClass = PCLASS_FIGHTER;
-    i = M_CheckParm("-class");
-
-    if (i > 0)
+    else
     {
-        pClass = atoi(myargv[i + 1]);
-        if (pClass > PCLASS_MAGE || pClass < PCLASS_FIGHTER)
-        {
-            I_Error("Invalid player class: %d\n", pClass);
-        }
-        ST_Message("\nPlayer Class: %d\n", pClass);
+        connect_data->player_class = PCLASS_FIGHTER;
     }
-    PlayerClass[consoleplayer] = pClass;
-    if (netgame)
-        D_ArbitrateNetStart();
-//ST_Message ("startskill %i  deathmatch: %i  startmap: %i  startepisode: %i\n", startskill, deathmatch, startmap, startepisode);
-
-// read values out of doomcom
-    ticdup = doomcom->ticdup;
-    maxsend = BACKUPTICS / (2 * ticdup) - 1;
-    if (maxsend < 1)
-        maxsend = 1;
-
-    for (i = 0; i < doomcom->numplayers; i++)
-        playeringame[i] = true;
-    for (i = 0; i < doomcom->numnodes; i++)
-        nodeingame[i] = true;
-
-//ST_Message ("player %i of %i (%i nodes)\n", consoleplayer+1, doomcom->numplayers, doomcom->numnodes);
-
 }
 
-/*
-==================
-=
-= D_QuitNetGame
-=
-= Called before quitting to leave a net game without hanging the
-= other players
-=
-==================
-*/
-
-void D_QuitNetGame(void)
+void D_InitSinglePlayerGame(net_gamesettings_t *settings)
 {
-    int i, j;
+    // default values for single player
 
-    if (debugfile)
-        fclose(debugfile);
+    settings->consoleplayer = 0;
+    settings->num_players = 1;
 
-    if (!netgame || !usergame || consoleplayer == -1 || demoplayback)
-        return;
+    netgame = false;
 
-// send a bunch of packets for security
-    netbuffer->player = consoleplayer;
-    netbuffer->numtics = 0;
-    for (i = 0; i < 4; i++)
+    //!
+    // @category net
+    //
+    // Start the game playing as though in a netgame with a single
+    // player.  This can also be used to play back single player netgame
+    // demos.
+    //
+
+    if (M_CheckParm("-solo-net") > 0)
     {
-        for (j = 1; j < doomcom->numnodes; j++)
-            if (nodeingame[j])
-                HSendPacket(j, NCMD_EXIT);
-        I_WaitVBL(1);
+        netgame = true;
     }
 }
 
-
-
-/*
-===============
-=
-= TryRunTics
-=
-===============
-*/
-
-int frametics[4], frameon;
-int frameskip[4];
-int oldnettics;
-extern boolean advancedemo;
-
-void TryRunTics(void)
-{
-    int i;
-    int lowtic;
-    int entertic;
-    static int oldentertics;
-    int realtics, availabletics;
-    int counts;
-    int numplaying;
-
 //
-// get real tics
+// D_CheckNetGame
+// Works out player numbers among the net participants
 //
-    entertic = I_GetTime() / ticdup;
-    realtics = entertic - oldentertics;
-    oldentertics = entertic;
 
-//
-// get available tics
-//
-    NetUpdate();
+void D_CheckNetGame (void)
+{
+    net_connect_data_t connect_data;
+    net_gamesettings_t settings;
 
-    lowtic = INT_MAX;
-    numplaying = 0;
-    for (i = 0; i < doomcom->numnodes; i++)
-        if (nodeingame[i])
-        {
-            numplaying++;
-            if (nettics[i] < lowtic)
-                lowtic = nettics[i];
-        }
-    availabletics = lowtic - gametic / ticdup;
+    D_RegisterLoopCallbacks(&hexen_loop_interface);
 
+    // Call D_QuitNetGame on exit 
 
-//
-// decide how many tics to run
-//
-    if (realtics < availabletics - 1)
-        counts = realtics + 1;
-    else if (realtics < availabletics)
-        counts = realtics;
-    else
-        counts = availabletics;
-    if (counts < 1)
-        counts = 1;
+    I_AtExit(D_QuitNetGame, true);
 
-    frameon++;
+    SaveGameSettings(&settings, &connect_data);
 
-    if (debugfile)
-        fprintf(debugfile, "=======real: %i  avail: %i  game: %i\n", realtics,
-                availabletics, counts);
-
-    if (!demoplayback)
+    if (D_InitNetGame(&connect_data, &settings))
     {
-        //=============================================================================
-        //
-        //      ideally nettics[0] should be 1 - 3 tics above lowtic
-        //      if we are consistantly slower, speed up time
-        //
-        for (i = 0; i < MAXPLAYERS; i++)
-            if (playeringame[i])
-                break;
-        if (consoleplayer == i)
-        {                       // the key player does not adapt
-        }
-        else
-        {
-            if (nettics[0] <= nettics[nodeforplayer[i]])
-            {
-                gametime--;
-                //                      printf ("-");
-            }
-            frameskip[frameon & 3] = (oldnettics > nettics[nodeforplayer[i]]);
-            oldnettics = nettics[0];
-            if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
-            {
-                skiptics = 1;
-                //                      printf ("+");
-            }
-        }
-        //=============================================================================
-    }                           // demoplayback
-
-    //
-    // wait for new tics if needed
-    //
-    while (lowtic < gametic / ticdup + counts)
+        netgame = true;
+        autostart = true;
+    }
+    else
     {
-
-        NetUpdate();
-        lowtic = INT_MAX;
-
-        for (i = 0; i < doomcom->numnodes; i++)
-            if (nodeingame[i] && nettics[i] < lowtic)
-                lowtic = nettics[i];
-
-        if (lowtic < gametic / ticdup)
-            I_Error("TryRunTics: lowtic < gametic");
-
-        // don't stay in here forever -- give the menu a chance to work
-        if (I_GetTime() / ticdup - entertic >= 20)
-        {
-            MN_Ticker();
-            return;
-        }
-
-        // Don't hog the CPU
-        I_Sleep(1);
+        D_InitSinglePlayerGame(&settings);
     }
 
+    LoadGameSettings(&settings, &connect_data);
+}
+
+//==========================================================================
 //
-// run the count * ticdup dics
+// NET_SendFrags
 //
-    while (counts--)
-    {
-        for (i = 0; i < ticdup; i++)
-        {
-            if (gametic / ticdup > lowtic)
-                I_Error("gametic>lowtic");
-            if (advancedemo)
-                H2_DoAdvanceDemo();
-            MN_Ticker();
-            G_Ticker();
-            gametic++;
-            //
-            // modify command for duplicated tics
-            //
-            if (i != ticdup - 1)
-            {
-                ticcmd_t *cmd;
-                int buf;
-                int j;
+//==========================================================================
 
-                buf = (gametic / ticdup) % BACKUPTICS;
-                for (j = 0; j < MAXPLAYERS; j++)
-                {
-                    cmd = &netcmds[j][buf];
-                    cmd->chatchar = 0;
-                    if (cmd->buttons & BT_SPECIAL)
-                        cmd->buttons = 0;
-                }
-            }
-        }
-        NetUpdate();            // check for new console commands
-    }
+void NET_SendFrags(player_t * player)
+{
+    // Not sure what this is intended for. Unused?
+}
+
+// TODO: This is a temporary hack!
+
+void DEH_Checksum(md5_digest_t digest)
+{
+    memset(digest, 0, sizeof(digest));
 }
--- a/src/hexen/g_game.c
+++ b/src/hexen/g_game.c
@@ -109,7 +109,8 @@
 
 boolean precache = true;        // if true, load all graphics at start
 
-short consistancy[MAXPLAYERS][BACKUPTICS];
+// TODO: Hexen uses 16-bit shorts for consistancy?
+byte consistancy[MAXPLAYERS][BACKUPTICS];
 
 int mouseSensitivity = 5;
 
@@ -195,9 +196,8 @@
 extern int inv_ptr;
 
 boolean usearti = true;
-void I_ReadCyberCmd(ticcmd_t * cmd);
 
-void G_BuildTiccmd(ticcmd_t * cmd)
+void G_BuildTiccmd(ticcmd_t *cmd, int maketic)
 {
     int i;
     boolean strafe, bstrafe;
@@ -888,7 +888,7 @@
         {
             cmd = &players[i].cmd;
 
-            memcpy(cmd, &netcmds[i][buf], sizeof(ticcmd_t));
+            memcpy(cmd, &netcmds[i], sizeof(ticcmd_t));
 
             if (demoplayback)
                 G_ReadDemoTiccmd(cmd);
--- a/src/hexen/h2_main.c
+++ b/src/hexen/h2_main.c
@@ -31,6 +31,7 @@
 #include <time.h>
 
 #include "config.h"
+#include "doomfeatures.h"
 
 #include "h2def.h"
 #include "ct_chat.h"
@@ -44,6 +45,7 @@
 #include "m_argv.h"
 #include "m_config.h"
 #include "m_controls.h"
+#include "net_client.h"
 #include "p_local.h"
 #include "v_video.h"
 #include "w_main.h"
@@ -66,7 +68,6 @@
 
 void R_ExecuteSetViewSize(void);
 void D_CheckNetGame(void);
-void G_BuildTiccmd(ticcmd_t * cmd);
 void F_Drawer(void);
 boolean F_Responder(event_t * ev);
 void I_StartupKeyboard(void);
@@ -314,6 +315,11 @@
     ST_Message("MN_Init: Init menu system.\n");
     MN_Init();
 
+#ifdef FEATURE_MULTIPLAYER
+    ST_Message("NET_Init: Init networking subsystem.\n");
+    NET_Init();
+#endif
+
     ST_Message("CT_Init: Init chat mode data.\n");
     CT_Init();
 
@@ -350,6 +356,15 @@
     // MAPINFO.TXT script must be already processed.
     WarpCheck();
 
+    ST_Message("SB_Init: Loading patches.\n");
+    SB_Init();
+
+    ST_Done();
+
+    // Netgame start must be here, after the splash screen has finished.
+    ST_Message("D_CheckNetGame: Checking network game status.\n");
+    D_CheckNetGame();
+
     if (autostart)
     {
         ST_Message("Warp to Map %d (\"%s\":%d), Skill %d\n",
@@ -356,14 +371,6 @@
                    WarpMap, P_GetMapName(startmap), startmap, startskill + 1);
     }
 
-    ST_Message("D_CheckNetGame: Checking network game status.\n");
-    D_CheckNetGame();
-
-    ST_Message("SB_Init: Loading patches.\n");
-    SB_Init();
-
-    ST_Done();
-
     CheckRecordFrom();
 
     p = M_CheckParm("-record");
@@ -566,24 +573,8 @@
         I_StartFrame();
 
         // Process one or more tics
-        if (singletics)
-        {
-            I_StartTic();
-            H2_ProcessEvents();
-            G_BuildTiccmd(&netcmds[consoleplayer][maketic % BACKUPTICS]);
-            if (advancedemo)
-            {
-                H2_DoAdvanceDemo();
-            }
-            G_Ticker();
-            gametic++;
-            maketic++;
-        }
-        else
-        {
-            // Will run at least one tic
-            TryRunTics();
-        }
+        // Will run at least one tic
+        TryRunTics();
 
         // Move positional sounds
         S_UpdateSounds(players[displayplayer].mo);
@@ -887,4 +878,3 @@
 {
     M_MakeDirectory(SavePath);
 }
-
--- a/src/hexen/h2def.h
+++ b/src/hexen/h2def.h
@@ -53,6 +53,9 @@
 
 #include "tables.h"
 
+#include "d_loop.h"
+#include "net_defs.h"
+
 #define HEXEN_VERSION 110
 #define HEXEN_VERSION_TEXT "v1.1"
 
@@ -95,7 +98,6 @@
 */
 
 //#define NUMARTIFCTS   28
-#define MAXPLAYERS	8
 
 #define	BT_ATTACK		1
 #define	BT_USE			2
@@ -575,59 +577,6 @@
 #define	CF_GODMODE		2
 #define	CF_NOMOMENTUM	4       // not really a cheat, just a debug aid
 
-
-#define		BACKUPTICS		12
-
-typedef struct
-{
-    unsigned checksum;          // high bit is retransmit request
-    byte retransmitfrom;        // only valid if NCMD_RETRANSMIT
-    byte starttic;
-    byte player, numtics;
-    ticcmd_t cmds[BACKUPTICS];
-} doomdata_t;
-
-typedef struct
-{
-    int id;
-    short intnum;               // DOOM executes an int to execute commands
-
-// communication between DOOM and the driver
-    short command;              // CMD_SEND or CMD_GET
-    short remotenode;           // dest for send, set by get (-1 = no packet)
-    short datalength;           // bytes in doomdata to be sent
-
-// info common to all nodes
-    short numnodes;             // console is allways node 0
-    short ticdup;               // 1 = no duplication, 2-5 = dup for slow nets
-    short extratics;            // 1 = send a backup tic in every packet
-    short deathmatch;           // 1 = deathmatch
-    short savegame;             // -1 = new game, 0-5 = load savegame
-    short episode;              // 1-3
-    short map;                  // 1-9
-    short skill;                // 1-5
-
-// info specific to this node
-    short consoleplayer;
-    short numplayers;
-    short angleoffset;          // 1 = left, 0 = center, -1 = right
-    short drone;                // 1 = drone
-
-// packet data to be sent
-    doomdata_t data;
-} doomcom_t;
-
-#define	DOOMCOM_ID		0x12345678l
-
-extern doomcom_t *doomcom;
-extern doomdata_t *netbuffer;   // points inside doomcom
-
-#define	MAXNETNODES		16      // max computers in a game
-
-#define	CMD_SEND	1
-#define	CMD_GET		2
-#define CMD_FRAG	3
-
 #define	SBARHEIGHT	39      // status bar height at bottom of screen
 
 void NET_SendFrags(player_t * player);
@@ -705,15 +654,9 @@
 extern int levelstarttic;       // gametic at level start
 extern int leveltime;           // tics in game play for par
 
-extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS];
-extern int ticdup;
+extern ticcmd_t *netcmds;
 
-//#define       MAXNETNODES             8
-
-extern ticcmd_t localcmds[BACKUPTICS];
 extern int rndindex;
-extern int gametic, maketic;
-extern int nettics[MAXNETNODES];
 
 #define MAXDEATHMATCHSTARTS 16
 extern mapthing_t *deathmatch_p;
--- a/src/net_client.c
+++ b/src/net_client.c
@@ -984,6 +984,7 @@
 
     server_addr = addr;
 
+    // TODO: Move into net_connect_data_t
     // Read checksums of our WAD directory and dehacked information
 
     W_Checksum(net_local_wad_md5sum);
@@ -1146,4 +1147,3 @@
 {
     M_BindVariable("player_name", &net_player_name);
 }
-
--- a/src/net_defs.h
+++ b/src/net_defs.h
@@ -145,8 +145,7 @@
     int lowres_turn;
     int drone;
     // TODO: is_freedoom in here?  WAD/DEH checksums?
-    // TODO: [Hexen] Requested player class
-
+    int player_class;
 } net_connect_data_t;
 
 // Game settings sent by client to server when initiating game start,
@@ -175,8 +174,10 @@
     int num_players;
     int consoleplayer;
 
-    // TODO: [Hexen] Array of player classes, one for each player.
+    // Hexen player classes:
 
+    int player_classes[MAXPLAYERS];
+
 } net_gamesettings_t;
 
 #define NET_TICDIFF_FORWARD      (1 << 0)
@@ -218,4 +219,3 @@
 } net_querydata_t;
 
 #endif /* #ifndef NET_DEFS_H */
-
--- a/src/net_server.c
+++ b/src/net_server.c
@@ -106,6 +106,10 @@
 
     unsigned int is_freedoom;
 
+    // Player class (for Hexen)
+
+    int player_class;
+
 } net_client_t;
 
 // structure used for the recv window
@@ -640,6 +644,7 @@
 
         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)
@@ -704,6 +709,20 @@
 
     settings.num_players = NET_SV_NumPlayers();
 
+    // Copy player classes:
+
+    for (i = 0; i < MAXPLAYERS; ++i)
+    {
+        if (sv_players[i] != NULL)
+        {
+            settings.player_classes[i] = sv_players[i]->player_class;
+        }
+        else
+        {
+            settings.player_classes[i] = 0;
+        }
+    }
+
     nowtime = I_GetTimeMS();
 
     // Send start packets to each connected node
@@ -1716,4 +1735,3 @@
         I_Sleep(1);
     }
 }
-
--- a/src/net_structrw.c
+++ b/src/net_structrw.c
@@ -37,6 +37,7 @@
     NET_WriteInt8(packet, data->gamemission);
     NET_WriteInt8(packet, data->lowres_turn);
     NET_WriteInt8(packet, data->drone);
+    NET_WriteInt8(packet, data->player_class);
 }
 
 boolean NET_ReadConnectData(net_packet_t *packet, net_connect_data_t *data)
@@ -44,11 +45,14 @@
     return NET_ReadInt8(packet, (unsigned int *) &data->gamemode)
         && NET_ReadInt8(packet, (unsigned int *) &data->gamemission)
         && NET_ReadInt8(packet, (unsigned int *) &data->lowres_turn)
-        && NET_ReadInt8(packet, (unsigned int *) &data->drone);
+        && NET_ReadInt8(packet, (unsigned int *) &data->drone)
+        && NET_ReadInt8(packet, (unsigned int *) &data->player_class);
 }
 
 void NET_WriteSettings(net_packet_t *packet, net_gamesettings_t *settings)
 {
+    int i;
+
     NET_WriteInt8(packet, settings->ticdup);
     NET_WriteInt8(packet, settings->extratics);
     NET_WriteInt8(packet, settings->deathmatch);
@@ -65,26 +69,50 @@
     NET_WriteInt8(packet, settings->loadgame);
     NET_WriteInt8(packet, settings->num_players);
     NET_WriteInt8(packet, settings->consoleplayer);
+
+    for (i = 0; i < settings->num_players; ++i)
+    {
+        NET_WriteInt8(packet, settings->player_classes[i]);
+    }
 }
 
 boolean NET_ReadSettings(net_packet_t *packet, net_gamesettings_t *settings)
 {
-    return NET_ReadInt8(packet, (unsigned int *) &settings->ticdup)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->extratics)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->deathmatch)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->nomonsters)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->fast_monsters)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->respawn_monsters)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->episode)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->map)
-        && NET_ReadSInt8(packet, &settings->skill)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->gameversion)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->lowres_turn)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->new_sync)
-        && NET_ReadInt32(packet, (unsigned int *) &settings->timelimit)
-        && NET_ReadSInt8(packet, (signed int *) &settings->loadgame)
-        && NET_ReadInt8(packet, (unsigned int *) &settings->num_players)
-        && NET_ReadSInt8(packet, (signed int *) &settings->consoleplayer);
+    boolean success;
+    int i;
+
+    success = NET_ReadInt8(packet, (unsigned int *) &settings->ticdup)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->extratics)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->deathmatch)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->nomonsters)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->fast_monsters)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->respawn_monsters)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->episode)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->map)
+           && NET_ReadSInt8(packet, &settings->skill)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->gameversion)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->lowres_turn)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->new_sync)
+           && NET_ReadInt32(packet, (unsigned int *) &settings->timelimit)
+           && NET_ReadSInt8(packet, (signed int *) &settings->loadgame)
+           && NET_ReadInt8(packet, (unsigned int *) &settings->num_players)
+           && NET_ReadSInt8(packet, (signed int *) &settings->consoleplayer);
+
+    if (!success)
+    {
+        return false;
+    }
+
+    for (i = 0; i < settings->num_players; ++i)
+    {
+        if (!NET_ReadInt8(packet,
+                          (unsigned int *) &settings->player_classes[i]))
+        {
+            return false;
+        }
+    }
+
+    return true;
 }
 
 boolean NET_ReadQueryData(net_packet_t *packet, net_querydata_t *query)
@@ -426,4 +454,3 @@
 
     putchar('\n');
 }
-