//-------------------------------------------------------------------------
/*
Copyright (C) 2017 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "duke3d.h"
#include "game.h"
#include "gamedef.h"
#include "net.h"
#include "premap.h"
#include "savegame.h"
#include "input.h"

#include "enet/enet.h"
#include "lz4.h"
#include "crc32.h"

#define SNAPSHOTS_TO_SAVE 32
#define MAX_SNAPSHOT_ACTORS 256
#define MAX_SNAPSHOT_WALLS MAXWALLS // 16384
#define MAX_SNAPSHOT_SECTORS MAXSECTORS // 4096

ENetHost    *g_netServer = NULL;
ENetHost    *g_netClient = NULL;
ENetPeer    *g_netClientPeer = NULL;
enet_uint16 g_netPort = 23513;
int32_t     g_netDisconnect = 0;
char        g_netPassword[32];
int32_t     g_netPlayersWaiting = 0;
int32_t     g_networkMode = NET_CLIENT;
int32_t     g_netIndex = 2;
newgame_t   pendingnewgame;

static int32_t NET_75_CHECK = 0;

// Client player ready for world updates (player spawned and ready)
static int ClientPlayerReady = 0;

// all zero NetActor
static const netactor_t cNullNetActor;

// all zero NetWall
static const netWall_t cNullNetWall;

// all zero NetSector
static const netSector_t cNullNetSector;



// Reserved revision numbers
// ------------------------------------------------------------------------------------------------------------------------------
// NOTE: Don't confuse these with indexes in the state array. Index 0 into the snapshot history is NOT the initial map state.
// g_MapStateHistory[0] could be revision 0, revision 64, revision 128, etc...

// This reserved revision number means that the state sent represents a diff from NullMapState, an all zero map state.
//      - Servers send this to reset players from scratch
//      - Clients set themselves to this on connect so they get a clean slate
//      - Clients will always accept this revision number and immediately start over
static const uint32_t cNoMapStateRevisionNumber = 0;

// This reserved revision number means that the diff sent by the server is against the "initial state" 
// that the server established from NullMapState (i.e., using cNoMapStateRevisionNumber).
//
// This is used for some cases when the client is more than NET_REVISIONS behind, or during a revision
// uint rollover.
static const uint32_t cMapStartStateRevisionNumber = 1;



// the revision array starts here instead of zero (may as well have the initial map state be revision 0)...
static const uint32_t cStartingRevisionIndex = 2;

static const int32_t cLocSprite_DeletedSpriteStat = MAXSTATUS;
static const int32_t cNetSprite_DeletedSpriteStat = STAT_NETALLOC;

//[75]  When a client attempts to allocate a sprite during the game loop, which is not defined as a clientside sprite,
//      the insert will go through, and the sprite will be put on this list.
//
//      every time the client gets a new snapshot, this entire list will be deleted

static int32_t headscratchpadsprite = -1;
static int32_t nextscratchpadsprite[MAXSPRITES];

// both the client and server store their current revision number here,
// when the client sends their revision to the server,
// the server stores it in g_player[that player's index].revision
uint32_t g_netMapRevisionNumber = cNoMapStateRevisionNumber;

// used as a diff target when the client has no snapshot
static netmapstate_t NullMapState;

// note that the map state number is not an index into here,
// to get the index into this array out of a map state number, do <Map state number> % NET_REVISONS
static netmapstate_t g_mapStateHistory[NET_REVISIONS];

// the state of the map after loading from file
static netmapstate_t g_mapStartState;

void DumpMapStateHistory()
{
    char* fileName = NULL;

    if (g_netClient)
    {
        fileName = "CL_MapStates.bin";
    }
    else
    {
        fileName = "SRV_MapStates.bin";
    }

    FILE* mapStatesFile = fopen(fileName, "wb");

    // write the null map state (it should never, ever be changed, but just for completeness sake
    fwrite(&NullMapState, sizeof(NullMapState), 1, mapStatesFile);

    fwrite(&g_mapStartState, sizeof(g_mapStartState), 1, mapStatesFile);

    fwrite(&g_mapStateHistory[0], sizeof(g_mapStateHistory), 1, mapStatesFile);

    OSD_Printf("Dumped map states to %s.\n", fileName);

    fclose(mapStatesFile);
    mapStatesFile = NULL;
}

static void DumpStatsAndSects()
{
    return;

    char* headSectFileName = NULL;
    char* headStatFileName = NULL;

    char* nextSectFileName = NULL;
    char* nextStatFileName = NULL;

    char* sprStatFileName = NULL;

    // what if the first entry was the rev#?

    if (g_netClient)
    {
        headSectFileName = "cl_headspritesect.csv";
        headStatFileName = "cl_headspritestat.csv";

        nextSectFileName = "cl_nextspritesect.csv";
        nextStatFileName = "cl_nextspritestat.csv";
        sprStatFileName = "cl_spritestat.csv";
    }
    else
    {
        headSectFileName = "srv_headspritesect.csv";
        headStatFileName = "srv_headspritestat.csv";

        nextSectFileName = "srv_nextspritesect.csv";
        nextStatFileName = "srv_nextspritestat.csv";

        sprStatFileName = "srv_spritestat.csv";
    }

    {

        FILE* nextsectCSVFile = fopen(nextSectFileName, "a+");
        
        fprintf(nextsectCSVFile, "%d ,", g_netMapRevisionNumber);

        FILE* nextstatCSVFile = fopen(nextStatFileName, "a+");

        fprintf(nextstatCSVFile, "%d ,", g_netMapRevisionNumber);

        FILE* spriteStatCSVFile = fopen(sprStatFileName, "a+");

        fprintf(spriteStatCSVFile, "%d ,", g_netMapRevisionNumber);

        for (int16_t arrIndex = 0; arrIndex < MAXSPRITES; arrIndex++)
        {
            int16_t nextSpriteSectIndex = nextspritesect[arrIndex];

            fprintf(nextsectCSVFile, "%d,", nextSpriteSectIndex);

            int16_t nextSpriteStatIndex = nextspritestat[arrIndex];

            fprintf(nextstatCSVFile, "%d,", nextSpriteStatIndex);

            fprintf(spriteStatCSVFile, "%d,", sprite[arrIndex].statnum);

           

        }

        fprintf(nextsectCSVFile, "\n");
        fprintf(nextstatCSVFile, "\n");
        fprintf(spriteStatCSVFile, "\n");

        fclose(nextsectCSVFile);
        fclose(nextstatCSVFile);
        fclose(spriteStatCSVFile);

    }

    {
        FILE* headsectCSV = fopen(headSectFileName, "a+");

        fprintf(headsectCSV, "%d ,", g_netMapRevisionNumber);

        for (int16_t sectIndex = 0; sectIndex < MAXSECTORS; sectIndex++)
        {
            int16_t headSpriteIndex = headspritesect[sectIndex];

            fprintf(headsectCSV, "%d,", headSpriteIndex);
        }

        fprintf(headsectCSV, "\n");

        fclose(headsectCSV);
    }

    {

        FILE* headstatCSV = fopen(headStatFileName, "a+");

        fprintf(headstatCSV, "%d ,", g_netMapRevisionNumber);

        for (int16_t statIndex = 0; statIndex < MAXSTATUS; statIndex++)
        {
            int16_t headSpriteIndex = headspritestat[statIndex];

            fprintf(headstatCSV, "%d,", headSpriteIndex);
        }

        fprintf(headstatCSV, "\n");

        fclose(headstatCSV);
    }


}

// debugging
static void Test_Iterate()
{
    if (!g_netClient)
    {
        return;
    }

    int32_t watchdogIndex = 0;
    const int32_t cThreshold = MAXSPRITES * 2;

    //    for (spriteIndex = headscratchpadsprite; spriteIndex >= 0; spriteIndex = nextscratchpadsprite[spriteIndex])

    for (int32_t statnum = 0; statnum < MAXSTATUS; statnum++)
    {
        watchdogIndex = 0;

        for (int32_t spriteIndex = headspritestat[statnum]; spriteIndex >= 0; spriteIndex = nextspritestat[spriteIndex])
        {
            watchdogIndex++;

            if (watchdogIndex > cThreshold)
            {
                Bassert(watchdogIndex <= cThreshold);
            }
        }
    }


    for (int32_t sectnum = 0; sectnum < MAXSECTORS; sectnum++)
    {
        watchdogIndex = 0;

        for (int32_t spriteIndex = headspritesect[sectnum]; spriteIndex >= 0; spriteIndex = nextspritesect[spriteIndex])
        {
            watchdogIndex++;

            if (watchdogIndex > cThreshold)
            {
                Bassert(watchdogIndex <= cThreshold);
            }
        }
    }
}



//Adds a sprite with index 'spriteIndex' to the netcode's internal scratch sprite list,
//this does NOT allocate a new sprite or insert it into the other arrays.
//
//ONLY use this to insert sprites inserted between snapshots that are NOT defined to be clientside.
void Net_InsertScratchPadSprite(int spriteIndex)
{
    if (!g_netClient)
    {
        return;
    }

    Bassert(spriteIndex < MAXSPRITES);
    Bassert(spriteIndex >= 0);

    int16_t const oldHead = headscratchpadsprite;

    nextscratchpadsprite[spriteIndex] = oldHead;

    headscratchpadsprite = spriteIndex;

    OSD_Printf("DEBUG: Inserted scratch pad sprite %d\n", spriteIndex);

}

void Net_DoDeleteSprite(int32_t spritenum)
{
    NET_75_CHECK++; // Need to add a check to Net_DoDeleteSprite so that the client does not give sprites that were not previously STAT_NETALLOC back to STAT_NETALLOC,
                    // otherwise the client is leaking non-STAT_NETALLOC sprites.
                    //
                    // may want to add a flag to actor_t for SFLAG_SCRATCHSPRITE and SFLAG_CLIENTSIDE

    if (sprite[spritenum].statnum == STAT_NETALLOC)
    {
        return;
    }

    changespritestat(spritenum, STAT_NETALLOC);
    do_deletespritesect(spritenum);
    sprite[spritenum].sectnum = MAXSECTORS;
}

void Net_InitScratchPadSpriteList()
{
    if (!g_netClient)
    {
        return;
    }

    headscratchpadsprite = -1;
    Bmemset(&nextscratchpadsprite[0], -1, sizeof(nextscratchpadsprite));
}

void Net_DeleteAllScratchPadSprites()
{
    if (!g_netClient)
    {
        return;
    }

    int16_t spriteIndex = headscratchpadsprite;

    for (spriteIndex = headscratchpadsprite; spriteIndex >= 0; spriteIndex = nextscratchpadsprite[spriteIndex])
    {
        Net_DoDeleteSprite(spriteIndex);
        OSD_Printf("DEBUG: Deleted scratch pad sprite (set to STAT_NETALLOC) %d\n", spriteIndex);
    }

    Net_InitScratchPadSpriteList();

    Test_Iterate();
}

//Deletes every single sprite in the game, this is needed when the client gets an initial snapshot
void Net_ClearAllLinkedLists()
{
    //Strangely enough, the build engine method is worse, causing a crash on the first shot fired by the client.
    initspritelists();

    
    Bmemset(&headspritestat[0], -1, sizeof(headspritestat));
    Bmemset(&headspritesect[0], -1, sizeof(headspritesect));

    Bmemset(&nextspritestat[0], -1, sizeof(nextspritestat));
    Bmemset(&nextspritesect[0], -1, sizeof(nextspritesect));

    Bmemset(&prevspritesect[0], -1, sizeof(prevspritesect));
    Bmemset(&prevspritestat[0], -1, sizeof(prevspritestat));

    tailspritefree = -1;
    

    // we need to at very least set every sprite to MAXSTATUS for its statnum,
    // so that the netcode knows that the sprite isn't part of any linked list 
    // and it needs to be inserted somewhere

    for (int16_t spriteIndex = 0; spriteIndex < MAXSPRITES; spriteIndex++)
    {
        sprite[spriteIndex].sectnum = MAXSECTORS;
        sprite[spriteIndex].statnum = MAXSTATUS;
    }
}



#ifdef NETCODE_DISABLE
void faketimerhandler(void) {}
#else

static char recbuf[180];
static int32_t g_chatPlayer = -1;

// sprites of these statnums are synced to clients by the server
int16_t g_netStatnums[] =
{
    STAT_PROJECTILE,
    //STAT_PLAYER, [75] Note that this was left out originally so that G_EnterLevel would be able to reset the player sprites befor Net_NotifyNewGame was ran.
    STAT_STANDABLE,
    //STAT_ACTIVATOR,
    //STAT_TRANSPORT,
    //STAT_EFFECTOR,
    STAT_ACTOR,
    STAT_ZOMBIEACTOR,
    STAT_MISC,
    STAT_DEFAULT,
    STAT_NETALLOC,
    MAXSTATUS
};

#define ARRAY_LEN(x)    (sizeof(x) / sizeof(*(x)))

// note that NETINDEX_BITS must be large enough to store the largest of
// MAXWALLS, MAXSECTORS, and MAXSPRITES, plus one bit so that the stop 
// code won't be mistaken for a valid index
//
// as of 5/4/2016 MAXSPRITES is 16384, which technically only needs 14 bits but I'm going to 
// set this to 16 + 1 to make it a more even number
//
// also NETINDEX_BITS should not exceed 32
#define NETINDEX_BITS (16 + 1)


//worst case: 2 bits per field per stuct if every field in every struct is changed
#define WORLD_CHANGEBITSSIZE (MAXWALLS * ARRAY_LEN(WallFields) * 2) + (MAXSECTORS * ARRAY_LEN(SectorFields) * 2) + (MAXSPRITES * ARRAY_LEN(ActorFields) * 2)

//                              each changed entry has a netindex + stop codes                  bits to indicate whether a field changed/zeroed                 convert to bytes....
#define WORLD_OVERHEADSIZE ((   (MAXSECTORS + MAXWALLS + MAXSPRITES + 3) * NETINDEX_BITS +      WORLD_CHANGEBITSSIZE)                                           >> 8) + 1

// actual game struct data size
#define WORLD_DATASIZE   (MAXSECTORS * sizeof(netSector_t) + MAXWALLS * sizeof(netWall_t) + MAXSPRITES * sizeof(netactor_t))

// max packet array size
#define	MAX_WORLDBUFFER  WORLD_DATASIZE + WORLD_OVERHEADSIZE

// debugging...
static const int64_t cWORLD_DataSize = WORLD_DATASIZE;
static const int64_t cWORLD_OverheadSize = WORLD_OVERHEADSIZE;
static const int64_t cWORLD_TotalSize = MAX_WORLDBUFFER;

// it's pretty big for now.
static const int64_t SnapshotArraySize = sizeof(netmapstate_t) * NET_REVISIONS;







uint8_t tempnetbuf[MAX_WORLDBUFFER];

// Remember that this constant needs to be one bit higher than netindexes, so it can't be mistaken for a valid wall, sprite, or sector index
static const int32_t cSTOP_PARSING_CODE = ((1 << NETINDEX_BITS) - 1);

//-------------------------------------------------------------------------
// Debugging 06052017 -- DELETE!
//-------------------------------------------------------------------------

int32_t Dbg_SprInSect[MAXSPRITES];
int32_t Dbg_SprInStat[MAXSPRITES];

int32_t Dbg_StatForSpr[MAXSPRITES];

const int32_t Dbg_STAT_PLAYER = STAT_PLAYER;

const int32_t Dbg_STAT_ACTOR = STAT_ACTOR;

const int32_t Dbg_APLAYER = APLAYER__STATIC;

const int32_t Dbg_HBONUS = 4991;

int Dbg_IsSpriteInSect(int32_t sectnum, int32_t spriteIndexToFind)
{
    int32_t currentSpriteIndex = 0;

    for (currentSpriteIndex = headspritesect[sectnum]; currentSpriteIndex >= 0; currentSpriteIndex = nextspritesect[currentSpriteIndex])
    {
        if (currentSpriteIndex == spriteIndexToFind)
        {
            return 1;
        }
    }

    return 0;

}

int Dbg_IsSpriteInStat(int32_t statnum, int32_t spriteIndexToFind)
{
    int32_t currentSpriteIndex = 0;

    for (currentSpriteIndex = headspritestat[statnum]; currentSpriteIndex >= 0; currentSpriteIndex = nextspritestat[currentSpriteIndex])
    {
        if (currentSpriteIndex == spriteIndexToFind)
        {
            return 1;
        }
    }

    return 0;

}

void Dbg_CheckForInfLoopInLinkedLists()
{
    int sectnum = 0;

    for (sectnum = 0; sectnum < numsectors; sectnum++)
    {
        int spriteIndex = 0;

        for (spriteIndex = headspritesect[sectnum]; spriteIndex >= 0; spriteIndex = nextspritesect[spriteIndex])
        {
            Bassert(spriteIndex != nextspritesect[spriteIndex]);
        }
    }

    int statnum = 0;

    for (statnum = 0; statnum < MAXSTATUS; statnum++)
    {
        int spriteIndex = 0;

        for (spriteIndex = headspritestat[statnum]; statnum >= 0; spriteIndex = nextspritestat[spriteIndex])
        {
            Bassert(spriteIndex != nextspritestat[spriteIndex]);
        }

    }
}

void Dbg_DumpSprInSect(int32_t sectnum)
{
    memset(&Dbg_SprInSect, -1234, sizeof(Dbg_SprInSect));
    memset(&Dbg_StatForSpr, -1234, sizeof(Dbg_StatForSpr));

    int32_t listIndex = 0;
    int32_t spriteIndex = 0;

    for (spriteIndex = headspritesect[sectnum]; spriteIndex >= 0; spriteIndex = nextspritesect[spriteIndex])
    {
        // infinite loop
        if (spriteIndex == nextspritesect[spriteIndex])
        {
            Dbg_SprInSect[listIndex] = -75;
            Dbg_StatForSpr[listIndex] = -75;
            return;
        }

        Dbg_SprInSect[listIndex] = spriteIndex;
        Dbg_StatForSpr[listIndex] = sprite[spriteIndex].statnum;
        listIndex++;
    }

}

void Dbg_DumpSprInStat(int32_t sectnum)
{
    memset(&Dbg_SprInStat, -1234, sizeof(Dbg_SprInStat));
    memset(&Dbg_StatForSpr, -1234, sizeof(Dbg_StatForSpr));

    int32_t listIndex = 0;
    int32_t spriteIndex = 0;

    for (spriteIndex = headspritestat[sectnum]; spriteIndex >= 0; spriteIndex = nextspritestat[spriteIndex])
    {
        // infinite loop
        if (spriteIndex == nextspritesect[spriteIndex])
        {
            Dbg_SprInSect[listIndex] = -75;
            Dbg_StatForSpr[listIndex] = -75;
            return;
        }

        Dbg_SprInStat[listIndex] = spriteIndex;
        Dbg_StatForSpr[listIndex] = sprite[spriteIndex].statnum;
        listIndex++;
    }

}
//-------------------------------------------------------------------------
// END DEBUGGING CRAP CODE
//-------------------------------------------------------------------------

void Net_Error_Disconnect(const char* message)
{
    OSD_Printf("Net_Error_Disconnect: %s\n", message);
    // Here I could longjmp to the game main loop and unload the map somehow 
    //
    // If we go to C++ it may be a good idea to throw an exception
    // 
    Bassert(0);
}

void Net_InitNetActor(netactor_t *netActor)
{
    *(netActor) = cNullNetActor;

    netActor->spr_statnum = MAXSTATUS;
    netActor->spr_sectnum = MAXSECTORS;
    netActor->netIndex = cSTOP_PARSING_CODE;
}

// Low level "Copy net structs to / from game structs" functions
//------------------------------------------------------------------------------
// Net -> Game Arrays
//------------------------------------------------------------------------------

void Net_CopyWallFromNet(netWall_t* netWall, walltype* gameWall)
{
    // (convert data from 32 bit integers)

    Bassert(netWall);
    Bassert(gameWall);

    gameWall->point2 = netWall->point2;
    gameWall->nextwall = netWall->nextwall;
    gameWall->nextsector = netWall->nextsector;

    gameWall->cstat = netWall->cstat;

    gameWall->picnum = netWall->picnum;
    gameWall->overpicnum = netWall->overpicnum;

    gameWall->shade = netWall->shade;

    gameWall->pal = netWall->pal;
    gameWall->xrepeat = netWall->xrepeat;
    gameWall->yrepeat = netWall->yrepeat;
    gameWall->xpanning = netWall->xpanning;
    gameWall->ypanning = netWall->ypanning;

    gameWall->lotag = netWall->lotag;
    gameWall->hitag = netWall->hitag;

    gameWall->extra = netWall->extra;

    int positionChanged = (netWall->x != gameWall->x) ||
        (netWall->y != gameWall->y);

    if (positionChanged)
    {
        dragpoint(netWall->netIndex, netWall->x, netWall->y, 0);
    }

    // unfortunately I don't know of any fields that would be guaranteed to be nonzero in here for error checking purposes.
    NET_75_CHECK++; // I should initialize netWall_t fields to some nonsense value other than zero, or maybe check for netWall->netIndex == cSTOP_PARSING_CODE?


}

void Net_CopySectorFromNet(netSector_t* netSector, sectortype* gameSector)
{
    Bassert(netSector);
    Bassert(gameSector);

    // (convert data from 32 bit integers)

    gameSector->wallptr = netSector->wallptr;
    gameSector->wallnum = netSector->wallnum;

    gameSector->ceilingz = netSector->ceilingz;
    gameSector->floorz = netSector->floorz;

    gameSector->ceilingstat = netSector->ceilingstat;
    gameSector->floorstat = netSector->floorstat;

    gameSector->ceilingpicnum = netSector->ceilingpicnum;
    gameSector->ceilingheinum = netSector->ceilingheinum;

    gameSector->ceilingshade = netSector->ceilingshade;

    gameSector->ceilingpal = netSector->ceilingpal;
    gameSector->ceilingxpanning = netSector->ceilingxpanning;
    gameSector->ceilingypanning = netSector->ceilingypanning;

    gameSector->floorpicnum = netSector->floorpicnum;
    gameSector->floorheinum = netSector->floorheinum;

    gameSector->floorshade = netSector->floorshade;

    gameSector->floorpal = netSector->floorpal;
    gameSector->floorxpanning = netSector->floorxpanning;
    gameSector->floorypanning = netSector->floorypanning;
    gameSector->visibility = netSector->visibility;
    gameSector->fogpal = netSector->fogpal;

    gameSector->lotag = netSector->lotag;
    gameSector->hitag = netSector->hitag;

    gameSector->extra = netSector->extra;

    // sanity check
    if (gameSector->wallnum <= 0)
    {
        Net_Error_Disconnect("Net_CopySectorFromNet: Invalid wallnum from server.");
    }

}



static void Net_UpdateSpriteLinkedLists(int16_t spriteIndex, const netactor_t* snapActor, const spritetype* gameSprite)
{
    if ((spriteIndex >= MAXSPRITES) || (spriteIndex < 0))
    {
        Net_Error_Disconnect("Can't update linked lists. Actor index invalid.");
    }

    //                                                              changes to game lists for this sprite:
    //
    //                                                                  stats       sectors
    //                                                              -------------------------
    // 1. Game sprite stat == snap sprite stat                      --  nothing     nothing
    //
    //
    // this leaks a STAT_NETALLOC
    // 2. game sprite <any stat>, snap sprite MAXSTATUS             --  delete      delete
    //
    // this leaks a client sprite
    // 3. game sprite MAXSTATUS, snap sprite STAT_NETALLOC or normal--  insert      insert
    //                           (implied in `else`)
    //
    // 4. game sprite STAT_NETALLOC, snap sprite Normal             --  change      insert
    //
    // 5. game sprite Normal, snap sprite STAT_NETALLOC             --  change      delete


    // [1]
    if (snapActor->spr_statnum == gameSprite->statnum)
    {
        return;
    }

    bool snapActorIsNormalStat =    (snapActor->spr_statnum != MAXSTATUS)   && (snapActor->spr_statnum != STAT_NETALLOC);
    bool gameSpriteIsNormalStat =   (gameSprite->statnum != MAXSTATUS)      && (gameSprite->statnum != STAT_NETALLOC);



    // [2]
    if (                                                snapActor->spr_statnum == MAXSTATUS)
    {
        OSD_Printf("DEBUG: Sprite %d: Case 2 (delete game sprite)\n", spriteIndex);

        do_deletespritestat(spriteIndex);
        do_deletespritesect(spriteIndex);
        
        return;
    }

    // [3]
    else if (gameSprite->statnum == MAXSTATUS)
    {
        OSD_Printf("DEBUG: Sprite %d: Case 3 (insert new game sprite)\n", spriteIndex);

        do_insertsprite_at_headofstat(spriteIndex, snapActor->spr_statnum);
        do_insertsprite_at_headofsect(spriteIndex, snapActor->spr_sectnum);
        
    }

    // [4]
    else if ((gameSprite->statnum == STAT_NETALLOC) &&  snapActorIsNormalStat)
    {   
        OSD_Printf("DEBUG: Sprite %d: Case 4 (STAT_NETALLOC to stat %d)\n", spriteIndex, snapActor->spr_statnum);

        changespritestat(spriteIndex, snapActor->spr_statnum);
        do_insertsprite_at_headofsect(spriteIndex, snapActor->spr_sectnum);
        
    }

    // [5]
    else if (gameSpriteIsNormalStat &&                  (snapActor->spr_statnum == STAT_NETALLOC))
    {
        OSD_Printf("DEBUG: Sprite %d: Case 5 (normal to STAT_NETALLOC)\n", spriteIndex);

        changespritestat(spriteIndex, snapActor->spr_statnum);
        do_deletespritesect(spriteIndex);
        
    }

    OSD_Printf("DEBUG: Sprite %d next is %d, prev is %d, head of stat %d is %d\n", 
                spriteIndex, nextspritestat[spriteIndex], prevspritestat[spriteIndex], snapActor->spr_statnum, headspritestat[snapActor->spr_statnum]);

    bool invalid = (
                        (nextspritesect[spriteIndex] == spriteIndex)
                        && (nextspritestat[spriteIndex] == spriteIndex)
                    );

    Bassert(!invalid);
        
    Test_Iterate();


}


static void Net_CopySpriteFromNet(const netactor_t* netActor, spritetype* gameSprite)
{
    Bassert(netActor);
    Bassert(gameSprite);

    // NOTE: Don't call Net_UpdateSpriteLinkedLists here, that should only be called
    //       if the sprite isn't already deleted in the game arrays.
    gameSprite->x = netActor->spr_x;
    gameSprite->y = netActor->spr_y;
    gameSprite->z = netActor->spr_z;



    gameSprite->statnum = netActor->spr_statnum;

    gameSprite->sectnum = netActor->spr_sectnum;


    gameSprite->cstat = netActor->spr_cstat;
    gameSprite->picnum = netActor->spr_picnum;
    gameSprite->shade = netActor->spr_shade;
    gameSprite->pal = netActor->spr_pal;
    gameSprite->clipdist = netActor->spr_clipdist;
    gameSprite->blend = netActor->spr_blend;

    gameSprite->xrepeat = netActor->spr_xrepeat;
    gameSprite->yrepeat = netActor->spr_yrepeat;

    gameSprite->xoffset = netActor->spr_xoffset;
    gameSprite->yoffset = netActor->spr_yoffset;



    gameSprite->ang = netActor->spr_ang;
    gameSprite->owner = netActor->spr_owner;
    gameSprite->xvel = netActor->spr_xvel;
    gameSprite->yvel = netActor->spr_yvel;
    gameSprite->zvel = netActor->spr_zvel;

    gameSprite->lotag = netActor->spr_lotag;
    gameSprite->hitag = netActor->spr_hitag;

    gameSprite->extra = netActor->spr_extra;
}



static void Net_CopyActorFromNet(const netactor_t* netActor, actor_t *gameActor)
{
    // (convert data from 32 bit integers)

    Bassert(netActor);
    Bassert(gameActor);

    int aiIDChanged = (gameActor->t_data[5] != netActor->t_data_5);

    // If the sprite is a CON sprite, don't overwrite AC_Action_Count
    int isActor = G_HaveActor(netActor->spr_picnum);

    gameActor->t_data[0] = netActor->t_data_0;
    gameActor->t_data[1] = netActor->t_data_1;

    if (aiIDChanged)
    {
        gameActor->t_data[2] = netActor->t_data_2;
    }

    if (aiIDChanged || !isActor)
    {
        gameActor->t_data[3] = netActor->t_data_3;
    }

    gameActor->t_data[4] = netActor->t_data_4;
    gameActor->t_data[5] = netActor->t_data_5;
    gameActor->t_data[6] = netActor->t_data_6;
    gameActor->t_data[7] = netActor->t_data_7;
    gameActor->t_data[8] = netActor->t_data_8;
    gameActor->t_data[9] = netActor->t_data_9;

#ifdef LUNATIC

    gameActor->mv.hvel = netActor->hvel;
    gameActor->mv.vvel = netActor->vvel;

    gameActor->ac.startframe = netActor->startframe;
    gameActor->ac.numframes = netActor->numframes;
    gameActor->ac.viewtype = netActor->viewtype;
    gameActor->ac.incval = netActor->incval;
    gameActor->ac.delay = netActor->delay;

    gameActor->actiontics = netActor->actiontics;

#endif

    gameActor->flags = netActor->flags;

    gameActor->bpos.x = netActor->bpos_x;
    gameActor->bpos.y = netActor->bpos_y;
    gameActor->bpos.z = netActor->bpos_z;

    gameActor->floorz = netActor->floorz;
    gameActor->ceilingz = netActor->ceilingz;
    gameActor->lastv.x = netActor->lastvx;
    gameActor->lastv.y = netActor->lastvy;

    gameActor->lasttransport = netActor->lasttransport;

    //WARNING: both sprite and actor have these fields
    gameActor->picnum = netActor->picnum;
    gameActor->ang = netActor->ang;
    gameActor->extra = netActor->extra;
    gameActor->owner = netActor->owner;

    gameActor->movflag = netActor->movflag;
    gameActor->tempang = netActor->tempang;
    gameActor->timetosleep = netActor->timetosleep;

    gameActor->actorstayput = netActor->actorstayput;
    gameActor->dispicnum = netActor->dispicnum;

#if defined LUNATIC

    //WARNING: NOT the same as movflag
    gameActor->movflags = netActor->movflags;

#endif

    gameActor->cgg = netActor->cgg;
}

static void Net_CopySpriteExtFromNet(const netactor_t* netActor, spriteext_t* gameSprExt)
{
    Bassert(netActor);
    Bassert(gameSprExt);

    gameSprExt->mdanimtims = netActor->ext_mdanimtims;

    gameSprExt->mdanimcur = netActor->ext_mdanimcur;
    gameSprExt->angoff = netActor->ext_angoff;
    gameSprExt->pitch = netActor->ext_pitch;
    gameSprExt->roll = netActor->ext_roll;

    gameSprExt->offset.x = netActor->ext_offset_x;
    gameSprExt->offset.y = netActor->ext_offset_y;
    gameSprExt->offset.z = netActor->ext_offset_z;

    gameSprExt->flags = netActor->ext_flags;
    gameSprExt->xpanning = netActor->ext_xpanning;
    gameSprExt->ypanning = netActor->ext_ypanning;

    gameSprExt->alpha = netActor->ext_alpha;

}

static void Net_CopySpriteSmoothFromNet(const netactor_t* netActor, spritesmooth_t* gameSprSmooth)
{
    Bassert(netActor);
    Bassert(gameSprSmooth);

    gameSprSmooth->smoothduration = netActor->sm_smoothduration;

    gameSprSmooth->mdcurframe = netActor->sm_mdcurframe;
    gameSprSmooth->mdoldframe = netActor->sm_mdoldframe;

    gameSprSmooth->mdsmooth = netActor->sm_mdsmooth;
}

void Net_CopyAllActorDataFromNet(const netactor_t* netActor, spritetype* gameSprite, actor_t* gameActor, spriteext_t* gameSprExt, spritesmooth_t* gameSprSmooth)
{
    Net_CopySpriteFromNet(netActor, gameSprite);
    Net_CopyActorFromNet(netActor, gameActor);
    Net_CopySpriteExtFromNet(netActor, gameSprExt);
    Net_CopySpriteSmoothFromNet(netActor, gameSprSmooth);

}

// Clients only.
static void Net_CopyPlayerSpriteFromNet(const netactor_t* netActor, spritetype* gameSprite)
{
    Bassert(netActor);
    Bassert(gameSprite);

    // We don't need to synchronize player position, ang, or sectnum, because P_ProcessInput
    // does that for all player sprites based on ps->pos and the player's input.

    gameSprite->cstat = netActor->spr_cstat;
    gameSprite->picnum = netActor->spr_picnum;
    gameSprite->shade = netActor->spr_shade;
    gameSprite->pal = netActor->spr_pal;
    gameSprite->clipdist = netActor->spr_clipdist;
    gameSprite->blend = netActor->spr_blend;

    gameSprite->xrepeat = netActor->spr_xrepeat;
    gameSprite->yrepeat = netActor->spr_yrepeat;

    gameSprite->xoffset = netActor->spr_xoffset;
    gameSprite->yoffset = netActor->spr_yoffset;

    gameSprite->owner = netActor->spr_owner;

    //xvel for player sprites is used for weapon bobbing, and is just set to the euclidean distance between
    //pos and bobpos, so there is no need to sync that.

    gameSprite->yvel = netActor->spr_yvel; // player index

                                           //zvel for player sprites is only used during the transition between air and water, I'm pretty sure that we don't think we need to sync this.

    gameSprite->lotag = netActor->spr_lotag;
    gameSprite->hitag = netActor->spr_hitag;

    gameSprite->extra = netActor->spr_extra;
}

// Similar to CopyAllActorData, but this one ignores some fields for the player sprite
void Net_CopyPlayerActorDataFromNet(const netactor_t* netActor, spritetype* gameSprite, actor_t* gameActor, spriteext_t* gameSprExt, spritesmooth_t* gameSprSmooth)
{
    Net_CopyPlayerSpriteFromNet(netActor, gameSprite);
    Net_CopyActorFromNet(netActor, gameActor);
    Net_CopySpriteExtFromNet(netActor, gameSprExt);
    Net_CopySpriteSmoothFromNet(netActor, gameSprSmooth);

}

void Net_CopyActorsToGameArrays(const netmapstate_t* snapshot)
{
    int32_t actorIndex = 0;
    int32_t actorCount = 0;

    // we need to clear out any sprites the client inserted between applying snapshots,
    // so that there aren't any sprite index conflicts.
    Net_DeleteAllScratchPadSprites();

    if ((snapshot->maxActorIndex) < 0 || (snapshot->maxActorIndex > MAXSPRITES))
    {
        Net_Error_Disconnect("Net_CopyActorsToGameArrays: Invalid number of actors in snapshot.");
    }

    // for right now
    Bassert(snapshot->maxActorIndex == MAXSPRITES);

    for (actorIndex = 0; actorIndex < MAXSPRITES; actorIndex++)
    {
        bool              isThisClientPlayerSprite = (actorIndex == g_player[myconnectindex].ps->i);

        const netactor_t*       snapshotActor = &(snapshot->actor[actorIndex]);

        spritetype*       gameSprite = &(sprite[actorIndex]);
        actor_t*          gameActor = &(actor[actorIndex]);
        spriteext_t*      gameExt = &(spriteext[actorIndex]);
        spritesmooth_t*   gameSm = &(spritesmooth[actorIndex]);

        int32_t           Dbg_GameSprOldStat = gameSprite->statnum;
        int32_t           Dbg_NetSprOldStat = snapshotActor->spr_statnum;

        bool              gameSpriteIsDeleted = (gameSprite->statnum == cNetSprite_DeletedSpriteStat) || (gameSprite->statnum == cLocSprite_DeletedSpriteStat);

        // NOTE: STAT_NETALLOC sprites ARE part if numsprites! We need to count STAT_NETALLOC sprites.
        bool              snapSpriteIsDeleted = (snapshotActor->spr_statnum == cLocSprite_DeletedSpriteStat);

        NET_75_CHECK++; // Need to make sure this will not effect swimming legs, holodukes, or start points negatively

        bool              isAnyOtherPlayerSprite = (snapshotActor->spr_picnum == APLAYER) && (snapshotActor->spr_yvel > 0);

        bool              isPlayer0Sprite = (actorIndex == g_player[0].ps->i);


        // it's better to let P_ProcessInput update some fields of the player's sprite.


        if (isPlayer0Sprite || isAnyOtherPlayerSprite)
        {
            NET_75_CHECK++; // Net_CopyPlayerActorDataFromNet() may be a good place to handle checking the player's new position
                            // this will also need updating when we support a dynamic number of player sprites...
            Net_CopyPlayerActorDataFromNet(snapshotActor, gameSprite, gameActor, gameExt, gameSm);

            continue;
        }

        Net_UpdateSpriteLinkedLists(actorIndex, snapshotActor, gameSprite);

        Net_CopyAllActorDataFromNet(snapshotActor, gameSprite, gameActor, gameExt, gameSm);

        if (!snapSpriteIsDeleted)
        {
            actorCount++;
        }

    }

    if (actorCount > MAXSPRITES)
    {
        Net_Error_Disconnect("Net_CopyActorsToGameArrays: Too many actors in snapshot.");
    }

    Numsprites = actorCount;

    DumpStatsAndSects();
}


//-------------------------------------------------------------------------------------
// Game -> Net
//-------------------------------------------------------------------------------------

void Net_CopyWallToNet(const walltype* gameWall, netWall_t* netWall, int16_t netIndex)
{
    Bassert(gameWall);
    Bassert(netWall);

    // (convert data to 32 bit integers)

    netWall->x = gameWall->x;
    netWall->y = gameWall->y;
    netWall->point2 = gameWall->point2;
    netWall->nextwall = gameWall->nextwall;
    netWall->nextsector = gameWall->nextsector;

    netWall->cstat = gameWall->cstat;
    netWall->picnum = gameWall->picnum;
    netWall->overpicnum = gameWall->overpicnum;
    netWall->shade = gameWall->shade;
    netWall->pal = gameWall->pal;

    netWall->xrepeat = gameWall->xrepeat;
    netWall->yrepeat = gameWall->yrepeat;
    netWall->xpanning = gameWall->xpanning;
    netWall->ypanning = gameWall->ypanning;
    netWall->lotag = gameWall->lotag;

    netWall->hitag = gameWall->hitag;
    netWall->extra = gameWall->extra;

    netWall->netIndex = netIndex;
}

void Net_CopySectorToNet(const sectortype * gameSector, netSector_t* netSector, int16_t netIndex)
{
    Bassert(gameSector);
    Bassert(netSector);

    // (convert data to 32 bit integers)

    netSector->wallptr = gameSector->wallptr;
    netSector->wallnum = gameSector->wallnum;
    netSector->ceilingz = gameSector->ceilingz;
    netSector->floorz = gameSector->floorz;
    netSector->ceilingstat = gameSector->ceilingstat;

    netSector->floorstat = gameSector->floorstat;
    netSector->ceilingpicnum = gameSector->ceilingpicnum;
    netSector->ceilingheinum = gameSector->ceilingheinum;
    netSector->ceilingshade = gameSector->ceilingshade;
    netSector->ceilingpal = gameSector->ceilingpal;

    netSector->ceilingxpanning = gameSector->ceilingxpanning;
    netSector->ceilingypanning = gameSector->ceilingypanning;
    netSector->floorpicnum = gameSector->floorpicnum;
    netSector->floorheinum = gameSector->floorheinum;
    netSector->floorshade = gameSector->floorshade;

    netSector->floorpal = gameSector->floorpal;
    netSector->floorxpanning = gameSector->floorxpanning;
    netSector->floorypanning = gameSector->floorypanning;
    netSector->visibility = gameSector->visibility;
    netSector->fogpal = gameSector->fogpal;

    netSector->lotag = gameSector->lotag;
    netSector->hitag = gameSector->hitag;
    netSector->extra = gameSector->extra;

    netSector->netIndex = netIndex;

}

static void Net_CopySpriteToNet(const spritetype* gameSprite, netactor_t* netActor)
{
    Bassert(netActor);
    Bassert(gameSprite);

    netActor->spr_x = gameSprite->x;
    netActor->spr_y = gameSprite->y;
    netActor->spr_z = gameSprite->z;

    netActor->spr_cstat = gameSprite->cstat;
    netActor->spr_picnum = gameSprite->picnum;
    netActor->spr_shade = gameSprite->shade;
    netActor->spr_pal = gameSprite->pal;
    netActor->spr_clipdist = gameSprite->clipdist;
    netActor->spr_blend = gameSprite->blend;

    netActor->spr_xrepeat = gameSprite->xrepeat;
    netActor->spr_yrepeat = gameSprite->yrepeat;

    netActor->spr_xoffset = gameSprite->xoffset;
    netActor->spr_yoffset = gameSprite->yoffset;

    netActor->spr_sectnum = gameSprite->sectnum;
    netActor->spr_statnum = gameSprite->statnum;

    netActor->spr_ang = gameSprite->ang;
    netActor->spr_owner = gameSprite->owner;
    netActor->spr_xvel = gameSprite->xvel;
    netActor->spr_yvel = gameSprite->yvel;
    netActor->spr_zvel = gameSprite->zvel;

    netActor->spr_lotag = gameSprite->lotag;
    netActor->spr_hitag = gameSprite->hitag;

    netActor->spr_extra = gameSprite->extra;

}

static void Net_CopyActorToNet(const actor_t* gameActor, netactor_t *netActor)
{
    // (convert data from 32 bit integers)

    Bassert(gameActor);
    Bassert(netActor);

    netActor->t_data_0 = gameActor->t_data[0];
    netActor->t_data_1 = gameActor->t_data[1];
    netActor->t_data_2 = gameActor->t_data[2];
    netActor->t_data_3 = gameActor->t_data[3];
    netActor->t_data_4 = gameActor->t_data[4];
    netActor->t_data_5 = gameActor->t_data[5];
    netActor->t_data_6 = gameActor->t_data[6];
    netActor->t_data_7 = gameActor->t_data[7];
    netActor->t_data_8 = gameActor->t_data[8];
    netActor->t_data_9 = gameActor->t_data[9];

#ifdef LUNATIC

    netActor->hvel = gameActor->mv.hvel;
    netActor->vvel = gameActor->mv.vvel;

    netActor->startframe = gameActor->ac.startframe;
    netActor->numframes = gameActor->ac.numframes;
    netActor->viewtype = gameActor->ac.viewtype;
    netActor->incval = gameActor->ac.incval;
    netActor->delay = gameActor->ac.delay;

    netActor->actiontics = gameActor->actiontics;

#endif

    netActor->flags = gameActor->flags;

    netActor->bpos_x = gameActor->bpos.x;
    netActor->bpos_y = gameActor->bpos.y;
    netActor->bpos_z = gameActor->bpos.z;

    netActor->floorz = gameActor->floorz;
    netActor->ceilingz = gameActor->ceilingz;
    netActor->lastvx = gameActor->lastv.x;
    netActor->lastvy = gameActor->lastv.y;

    netActor->lasttransport = gameActor->lasttransport;

    //WARNING: both sprite and actor have these fields
    netActor->picnum = gameActor->picnum;
    netActor->ang = gameActor->ang;
    netActor->extra = gameActor->extra;
    netActor->owner = gameActor->owner;

    netActor->movflag = gameActor->movflag;
    netActor->tempang = gameActor->tempang;
    netActor->timetosleep = gameActor->timetosleep;

    netActor->actorstayput = gameActor->actorstayput;
    netActor->dispicnum = gameActor->dispicnum;

#if defined LUNATIC
    //WARNING: NOT the same as movflag
    netActor->movflags = gameActor->movflags;

#endif

    netActor->cgg = gameActor->cgg;
}

static void Net_CopySpriteExtToNet(const spriteext_t* gameSpriteExt, netactor_t* netActor)
{
    Bassert(gameSpriteExt);
    Bassert(netActor);

    netActor->ext_mdanimtims = gameSpriteExt->mdanimtims;

    netActor->ext_mdanimcur = gameSpriteExt->mdanimcur;
    netActor->ext_angoff = gameSpriteExt->angoff;
    netActor->ext_pitch = gameSpriteExt->pitch;
    netActor->ext_roll = gameSpriteExt->roll;

    netActor->ext_offset_x = gameSpriteExt->offset.x;
    netActor->ext_offset_y = gameSpriteExt->offset.y;
    netActor->ext_offset_z = gameSpriteExt->offset.z;

    netActor->ext_flags = gameSpriteExt->flags;
    netActor->ext_xpanning = gameSpriteExt->xpanning;
    netActor->ext_ypanning = gameSpriteExt->ypanning;

    netActor->ext_alpha = gameSpriteExt->alpha;
}

static void Net_CopySpriteSmoothToNet(const spritesmooth_t* gameSprSmooth, netactor_t* netActor)
{
    Bassert(gameSprSmooth);
    Bassert(netActor);

    netActor->sm_smoothduration = gameSprSmooth->smoothduration;
    netActor->sm_mdcurframe = gameSprSmooth->mdcurframe;
    netActor->sm_mdoldframe = gameSprSmooth->mdoldframe;
    netActor->sm_mdsmooth = gameSprSmooth->mdsmooth;

}

void Net_CopyAllActorDataToNet(int32_t spriteIndex, const spritetype* gameSprite, const actor_t* gameActor, const spriteext_t* gameSprExt, const spritesmooth_t* gameSprSmooth, netactor_t* netActor)
{
    Net_CopySpriteToNet(gameSprite, netActor);
    Net_CopyActorToNet(gameActor, netActor);
    Net_CopySpriteExtToNet(gameSprExt, netActor);
    Net_CopySpriteSmoothToNet(gameSprSmooth, netActor);

    netActor->netIndex = spriteIndex;

}

static void Net_AddActorsToSnapshot(netmapstate_t* snapshot)
{

    int32_t gameIndex = 0;

    NET_75_CHECK++; // we may want to only send over sprites that are visible, this might be a good optimization
                    // to do later.

    NET_75_CHECK++; // Verify: Does the netcode need to worry about spriteext and spritesmooth beyond index (MAXSPRITES - 1)?

    NET_75_CHECK++; // may be able to significantly improve performance by using headspritestat[] etc. lists
                    // i.e., replace with for(all stat) { for(all sprites in stat) { } }, ignoring net Non Relevant Stats,
                    // also then ioSnapshot->maxActorIndex could be something much less than MAXSPRITES

    snapshot->maxActorIndex = 0;

    // note that Numsprites should NOT be the upper bound, if sprites are deleted in the middle
    // the max index to check will be > than Numsprites.
    for (gameIndex = 0; gameIndex < (MAXSPRITES); gameIndex++)
    {
        const spritetype*       gameSpr = &sprite[gameIndex];

        const actor_t*          gameAct = &actor[gameIndex];
        const spriteext_t*      gameExt = &spriteext[gameIndex];
        const spritesmooth_t*   gameSmooth = &spritesmooth[gameIndex];

        netactor_t*       netSprite = &snapshot->actor[gameIndex];


        Net_CopyAllActorDataToNet(gameIndex, gameSpr, gameAct, gameExt, gameSmooth, netSprite);

    }

    snapshot->maxActorIndex = MAXSPRITES;

}




static void Net_AddWorldToSnapshot(netmapstate_t* snapshot)
{
    int32_t index = 0;

    for (index = 0; index < numwalls; index++)
    {
        // on the off chance that numwalls somehow gets set to higher than MAXWALLS... somehow...
        Bassert(index < MAXWALLS);
        const	walltype*   gameWall = &wall[index];
        netWall_t*  snapshotWall = &snapshot->wall[index];

        Net_CopyWallToNet(gameWall, snapshotWall, index);


    }

    for (index = 0; index < numsectors; index++)
    {
        Bassert(index < MAXSECTORS);
        const	sectortype*  gameSector = &sector[index];
        netSector_t* snapshotSector = &snapshot->sector[index];

        Net_CopySectorToNet(gameSector, snapshotSector, index);

    }

    Net_AddActorsToSnapshot(snapshot);
}

void Net_AddWorldAsStartState()
{
    Net_AddWorldToSnapshot(&g_mapStartState);
    g_netMapRevisionNumber = cStartingRevisionIndex; // indicate that the server is beyond the initial snapshot and should start sending out world updates
}

//------------------------------------------------------------------------------------------------------------------------


static void P_RemovePlayer(int32_t p)
{
    // server obviously can't leave the game, and index 0 shows up for disconnect events from
    // players that haven't gotten far enough into the connection process to get a player ID

    if (p <= 0) return;

    g_player[p].playerquitflag = 0;

    Bsprintf(recbuf,"%s^00 is history!",g_player[p].user_name);
    G_AddUserQuote(recbuf);

    if (numplayers == 1)
        S_PlaySound(GENERIC_AMBIENCE17);

    if (g_player[myconnectindex].ps->gm & MODE_GAME)
    {
        if (screenpeek == p)
            screenpeek = myconnectindex;

        pub = NUMPAGES;
        pus = NUMPAGES;
        G_UpdateScreenArea();

        P_QuickKill(g_player[p].ps);

        if (voting == p)
        {
            for (p=0; p<MAXPLAYERS; p++)
            {
                g_player[p].vote = 0;
                g_player[p].gotvote = 0;
            }
            voting = -1;
        }

        Bstrcpy(apStrings[QUOTE_RESERVED2],recbuf);
        g_player[myconnectindex].ps->ftq = QUOTE_RESERVED2;
        g_player[myconnectindex].ps->fta = 180;
    }
}

// sync a connecting player up with the current game state
void Net_SyncPlayer(ENetEvent *event)
{
    int32_t newPlayerIndex, j;

    if (numplayers + g_netPlayersWaiting >= MAXPLAYERS)
    {
        enet_peer_disconnect_later(event->peer, DISC_SERVER_FULL);
        initprintf("Refused peer; server full.\n");
        return;
    }

    g_netPlayersWaiting++;

    S_PlaySound(DUKE_GETWEAPON2);

    // open a new slot if necessary and save off the resulting slot # for future reference
    for (TRAVERSE_CONNECT(newPlayerIndex))
    {
        if (g_player[newPlayerIndex].playerquitflag == 0)
        {
            break;
        }
    }

    if (newPlayerIndex == -1)
    {
        newPlayerIndex = g_mostConcurrentPlayers++;
    }

    NET_75_CHECK++; // is it necessary to se event->peer->data to the new player index in Net_SyncPlayer?
    event->peer->data = (void *)(intptr_t)newPlayerIndex;

    g_player[newPlayerIndex].revision = cNoMapStateRevisionNumber;
    g_player[newPlayerIndex].netsynctime = totalclock;
    g_player[newPlayerIndex].playerquitflag = 1;

    NET_75_CHECK++; // Need to think of something better when finding a remaining slot for players.

    for (j = 0; j < g_mostConcurrentPlayers - 1; j++)
    {
        connectpoint2[j] = j + 1;
    }

    connectpoint2[g_mostConcurrentPlayers - 1] = -1;

    G_MaybeAllocPlayer(newPlayerIndex);

    g_netPlayersWaiting--;
    ++numplayers;
    ++ud.multimode;
    Net_SendNewPlayer(newPlayerIndex);
    Net_SendPlayerIndex(newPlayerIndex, event->peer);
    Net_SendClientInfo();
    Net_SendUserMapName();
    Net_SendNewGame(0, event->peer); // newly connecting player (Net_SyncPlayer)
}

void Net_SpawnPlayer(int32_t player)
{
    int32_t byteOffset = 0;
    packbuf[byteOffset++] = PACKET_PLAYER_SPAWN;
    packbuf[byteOffset++] = player;

    Bmemcpy(&packbuf[byteOffset], &g_player[player].ps->pos.x, sizeof(vec3_t) * 2);
    byteOffset += sizeof(vec3_t) * 2;

    packbuf[byteOffset++] = 0;

    enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], byteOffset, ENET_PACKET_FLAG_RELIABLE));

    Dbg_PacketSent(PACKET_PLAYER_SPAWN);

}

static void display_betascreen(void)
{
    rotatesprite_fs(160 << 16, 100 << 16, 65536, 0, BETASCREEN, 0, 0, 2 + 8 + 64 + BGSTRETCH);

    rotatesprite_fs(160 << 16, (104) << 16, 60 << 10, 0, DUKENUKEM, 0, 0, 2 + 8);
    rotatesprite_fs(160 << 16, (129) << 16, 30 << 11, 0, THREEDEE, 0, 0, 2 + 8);
    if (PLUTOPAK)   // JBF 20030804
        rotatesprite_fs(160 << 16, (151) << 16, 30 << 11, 0, PLUTOPAKSPRITE + 1, 0, 0, 2 + 8);
}

void faketimerhandler(void)
{
    if (g_netServer == NULL && g_netClient == NULL)
        return;

    enet_host_service(g_netServer ? g_netServer : g_netClient, NULL, 0);
}

void Net_WaitForServer(void)
{
    int32_t serverReady = g_player[0].pingcnt;

    if (numplayers < 2 || g_netServer) return;

    P_SetGamePalette(g_player[myconnectindex].ps, TITLEPAL, 8 + 2 + 1);

    do
    {
        if (quitevent || keystatus[1]) G_GameExit("");

        if (G_FPSLimit())
        {
            display_betascreen();
            gametext_center_shade(170, "Waiting for server", 14);
        }

        // XXX: this looks like something that should be rate limited...
        packbuf[0] = PACKET_PLAYER_PING;
        packbuf[1] = myconnectindex;

        if (g_netClientPeer)
        {
            enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
            Dbg_PacketSent(PACKET_PLAYER_PING);
        }



        G_HandleAsync();

        if (g_player[0].pingcnt > serverReady)
        {
            P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 8 + 2 + 1);
            return;
        }
    } while (1);
}

void Net_ResetPrediction(void)
{
    NET_75_CHECK++; // this function is probably not needed

}

////////////////////////////////////////////////////////////////////////////////
// Connect/Disconnect

void Net_Connect(const char *srvaddr)
{
    ENetAddress address;
    ENetEvent event;
    char *addrstr = NULL;

    const int32_t cNumberOfRetries = 4;

    int32_t connectCount = cNumberOfRetries;

    char *oursrvaddr = Xstrdup(srvaddr);

    Net_Disconnect();

    g_netClient = enet_host_create(NULL, 1, CHAN_MAX, 0, 0);

    if (g_netClient == NULL)
    {
        initprintf("An error occurred while trying to create an ENet client host.\n");
        return;
    }

    addrstr = strtok(oursrvaddr, ":");
    enet_address_set_host(&address, addrstr);
    addrstr = strtok(NULL, ":");
    address.port = addrstr == NULL ? g_netPort : Batoi(addrstr);

    g_netClientPeer = enet_host_connect(g_netClient, &address, CHAN_MAX, 0);

    if (g_netClientPeer == NULL)
    {
        initprintf("No available peers for initiating an ENet connection.\n");
        return;
    }

    for (connectCount = cNumberOfRetries; connectCount > 0; connectCount--)
    {
        /* Wait up to 5 seconds for the connection attempt to succeed. */
        if (enet_host_service(g_netClient, &event, 5000) > 0 &&
            event.type == ENET_EVENT_TYPE_CONNECT)
        {
            initprintf("Connection to %s:%d succeeded.\n", oursrvaddr, address.port);
            Bfree(oursrvaddr);
            return;
        }
        else
        {
            /* Either the 5 seconds are up or a disconnect event was */
            /* received. Reset the peer in the event the 5 seconds   */
            /* had run out without any significant event.            */
            enet_peer_reset(g_netClientPeer);
            initprintf("Connection to %s:%d failed.\n", oursrvaddr, address.port);
        }
        initprintf(connectCount ? "Retrying...\n" : "Giving up connection attempt.\n");
    }

    // [75] note: it only gets here if there was an error
    Bfree(oursrvaddr);
    Net_Disconnect();
}

void Net_Disconnect(void)
{
    if (g_netClient)
    {
        ENetEvent event;

        if (g_netClientPeer)
            enet_peer_disconnect_later(g_netClientPeer, 0);

        while (enet_host_service(g_netClient, &event, 3000) > 0)
        {
            switch (event.type)
            {
            case ENET_EVENT_TYPE_CONNECT:
            case ENET_EVENT_TYPE_NONE:
            case ENET_EVENT_TYPE_RECEIVE:
                if (event.packet)
                    enet_packet_destroy(event.packet);
                break;

            case ENET_EVENT_TYPE_DISCONNECT:
                numplayers = g_mostConcurrentPlayers = ud.multimode = 1;
                myconnectindex = screenpeek = 0;
                G_BackToMenu();
                break;
            }
        }

        enet_peer_reset(g_netClientPeer);
        g_netClientPeer = NULL;
        enet_host_destroy(g_netClient);
        g_netClient = NULL;
        return;
    }

    if (g_netServer)
    {
        int32_t peerIndex;
        ENetEvent event;

        for (peerIndex = 0; peerIndex < (signed)g_netServer->peerCount; peerIndex++)
        {
            enet_peer_disconnect_later(&g_netServer->peers[peerIndex], DISC_SERVER_QUIT);
        }
        while (enet_host_service(g_netServer, &event, 3000) > 0)
        {
            switch (event.type)
            {
            case ENET_EVENT_TYPE_CONNECT:
            case ENET_EVENT_TYPE_NONE:
            case ENET_EVENT_TYPE_RECEIVE:
            case ENET_EVENT_TYPE_DISCONNECT:
                if (event.packet)
                {
                    enet_packet_destroy(event.packet);
                }
                break;
            }
        }
        enet_host_destroy(g_netServer);
        g_netServer = NULL;
    }
}

// Only clients run this
void Net_ReceiveDisconnect(ENetEvent *event)
{
    g_netDisconnect = 1;
    numplayers = g_mostConcurrentPlayers = ud.multimode = 1;
    myconnectindex = screenpeek = 0;
    G_BackToMenu();

    switch (event->data)
    {
    case DISC_BAD_PASSWORD:
        initprintf("Bad password.\n");
        return;
    case DISC_VERSION_MISMATCH:
        initprintf("Version mismatch.\n");
        return;
    case DISC_INVALID:
        initprintf("Invalid data detected.\n");
        return;
    case DISC_SERVER_QUIT:
        initprintf("The server is quitting.\n");
        return;
    case DISC_SERVER_FULL:
        initprintf("The server is full.\n");
        return;
    case DISC_KICKED:
        initprintf("You have been kicked from the server.\n");
        return;
    case DISC_BANNED:
        initprintf("You are banned from this server.\n");
        return;
    default:
        initprintf("Disconnected.\n");
        return;
    }
}

////////////////////////////////////////////////////////////////////////////////
// Packet Handlers

#endif

void Net_GetPackets(void)
{
    sampletimer();
    MUSIC_Update();
    S_Update();

    G_HandleSpecialKeys();

    if (g_netDisconnect)
    {
        Net_Disconnect();
        g_netDisconnect = 0;

        if (g_gameQuit)
            G_GameExit(" ");

        return;
    }

    if (g_netServer)
    {
        Net_HandleClientPackets();
    }
    else if (g_netClient)
    {
        Net_HandleServerPackets();
    }
}

#ifndef NETCODE_DISABLE

void Net_HandleClientPackets(void)
{
    ENetEvent event;

    // pull events from the wire into the packet queue without dispatching them, once per Net_GetPackets() call
    enet_host_service(g_netServer, NULL, 0);

    // dispatch any pending events from the local packet queue
    while (enet_host_check_events(g_netServer, &event) > 0)
    {
        const intptr_t playeridx = (intptr_t)event.peer->data;

        if (playeridx < 0 || playeridx >= MAXPLAYERS)
        {
            enet_peer_disconnect_later(event.peer, DISC_INVALID);
            buildprint("Invalid player id (", playeridx, ") from client.\n");
            continue;
        }

        switch (event.type)
        {
        case ENET_EVENT_TYPE_CONNECT:
        {
            char ipaddr[32];

            enet_address_get_host_ip(&event.peer->address, ipaddr, sizeof(ipaddr));

            OSD_Printf("A new client connected from %s:%u.\n", ipaddr, event.peer->address.port);

            //[75] Temporary: For now the netcode can't handle more players connecting than there are player starts.
            if (g_playerSpawnCnt <= ud.multimode)
            {
                OSD_Printf("Connection dropped: No player spawn point available for this new player.");
                break;
            }

            Net_SendAcknowledge(event.peer);
            break;
        }

        case ENET_EVENT_TYPE_RECEIVE:
            /*
            initprintf ("A packet of length %u containing %s was received from player %d on channel %u.\n",
            event.packet -> dataLength,
            event.packet -> data,
            event.peer -> data,
            event.channelID);
            */
            Net_ParseClientPacket(&event);
            // broadcast takes care of enet_packet_destroy itself
            // we set the state to disconnected so enet_host_broadcast
            // doesn't send the player back his own packets
            if ((event.channelID == CHAN_GAMESTATE && event.packet->data[0] > PACKET_BROADCAST)
                || event.channelID == CHAN_CHAT)
            {
                const ENetPacket *pak = event.packet;

                event.peer->state = ENET_PEER_STATE_DISCONNECTED;
                enet_host_broadcast(g_netServer, event.channelID,
                    enet_packet_create(pak->data, pak->dataLength, pak->flags&ENET_PACKET_FLAG_RELIABLE));
                event.peer->state = ENET_PEER_STATE_CONNECTED;
            }

            enet_packet_destroy(event.packet);
            g_player[playeridx].ping = (event.peer->lastRoundTripTime + event.peer->roundTripTime) / 2;
            break;

        case ENET_EVENT_TYPE_DISCONNECT:
            numplayers--;
            ud.multimode--;

            P_RemovePlayer(playeridx);

            g_player[playeridx].revision = cNoMapStateRevisionNumber;

            packbuf[0] = PACKET_PLAYER_DISCONNECTED;
            packbuf[1] = playeridx;
            packbuf[2] = numplayers;
            packbuf[3] = ud.multimode;
            packbuf[4] = g_mostConcurrentPlayers;
            packbuf[5] = myconnectindex;

            enet_host_broadcast(g_netServer, CHAN_GAMESTATE,
                enet_packet_create(&packbuf[0], 6, ENET_PACKET_FLAG_RELIABLE));

            initprintf("%s disconnected.\n", g_player[playeridx].user_name);
            event.peer->data = NULL;

            Dbg_PacketSent(PACKET_PLAYER_DISCONNECTED);

            break;

        default:
            break;
        }
    }
}

void Net_HandleServerPackets(void)
{
    ENetEvent event;

    enet_host_service(g_netClient, NULL, 0);

    while (enet_host_check_events(g_netClient, &event) > 0)
    {
        if (event.type == ENET_EVENT_TYPE_DISCONNECT)
        {
            Net_ReceiveDisconnect(&event);
        }
        else if (event.type == ENET_EVENT_TYPE_RECEIVE)
        {
            Net_ParseServerPacket(&event);
        }

        enet_packet_destroy(event.packet);
    }
}

void Net_Server_SetupPlayer(int playerindex)
{
    int16_t playerspriteindex;

    playerspriteindex = g_player[playerindex].ps->i;
    Bmemcpy(g_player[playerindex].ps, g_player[0].ps, sizeof(DukePlayer_t));

    g_player[playerindex].ps->i = playerspriteindex;
    changespritestat(playerspriteindex, STAT_PLAYER);

    g_player[playerindex].ps->last_extra = sprite[g_player[playerindex].ps->i].extra = g_player[playerindex].ps->max_player_health;
    sprite[g_player[playerindex].ps->i].cstat = 1 + 256;
    actor[g_player[playerindex].ps->i].t_data[2] = actor[g_player[playerindex].ps->i].t_data[3] = actor[g_player[playerindex].ps->i].t_data[4] = 0;

    P_ResetPlayer(playerindex);
    Net_SpawnPlayer(playerindex);

}

void Net_ParseClientPacket(ENetEvent *event)
{
    uint8_t *pbuf = event->packet->data;
    int32_t packbufleng = event->packet->dataLength;

    int32_t other = pbuf[--packbufleng];

    enum DukePacket_t tPacketType = (enum DukePacket_t) pbuf[0];

#if 0
    initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng);
#endif
    switch (pbuf[0])
    {
    case PACKET_SLAVE_TO_MASTER:  //[1] (receive slave sync buffer)
        Net_ReceiveClientUpdate(event);
        break;

    case PACKET_PLAYER_READY:
    {
        if ((other == 0) || (other == myconnectindex) || (other >= MAXPLAYERS))
        {
            break;
        }

        // At intermission, just set the ready flag and wait for the other players.
        if (g_player[myconnectindex].ps->gm & MODE_EOL)
        {
            g_player[other].ready = 1;
        }
        else
        {
            // otherwise if the server's in a map now, set up the player immediately.
            Net_Server_SetupPlayer(other);
        }


        break;
    }

    case PACKET_PLAYER_PING:
        if (g_player[myconnectindex].ps->gm & MODE_GAME)
        {
            packbuf[0] = PACKET_PLAYER_PING;
            packbuf[1] = myconnectindex;
            enet_peer_send(event->peer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));

            Dbg_PacketSent(PACKET_PLAYER_PING);
        }
        g_player[other].pingcnt++;
        break;

    case PACKET_AUTH:
        Net_ReceiveChallenge(pbuf, packbufleng, event);
        break;

    default:
        Net_ParsePacketCommon(pbuf, packbufleng, 0);
        break;
    }
}

void Net_ParseServerPacket(ENetEvent *event)
{
    uint8_t *pbuf = event->packet->data;
    int32_t packbufleng = event->packet->dataLength;
    // input_t *nsyn;

    enum DukePacket_t tPacketType = (enum DukePacket_t) pbuf[0];

    --packbufleng;  //    int32_t other = pbuf[--packbufleng];

#if 0
    initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng);
#endif
    switch (pbuf[0])
    {
    case PACKET_MASTER_TO_SLAVE:

        if (!(g_player[myconnectindex].ps->gm & MODE_GAME))
        {
            return;
        }

        Net_ReceiveServerUpdate(event);
        break;

    case PACKET_NEW_GAME:
        Net_ReceiveNewGame(event);
        break;

    case PACKET_ACK:
        Net_ReceiveAcknowledge(pbuf, packbufleng);
        break;

    case PACKET_NUM_PLAYERS:
        Net_ReceiveNewPlayer(event->packet->data, event->packet->dataLength);
        break;

    case PACKET_PLAYER_INDEX:
        Net_ReceivePlayerIndex(event->packet->data, event->packet->dataLength);
        break;

    case PACKET_PLAYER_DISCONNECTED:
        if ((g_player[myconnectindex].ps->gm & MODE_GAME))
            P_RemovePlayer(pbuf[1]);
        numplayers = pbuf[2];
        ud.multimode = pbuf[3];
        g_mostConcurrentPlayers = pbuf[4];
        break;

    case PACKET_PLAYER_SPAWN:
        if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) break;

        // testing
        if(g_netServer)
        {
            P_ResetPlayer(pbuf[1]);
        }
        Bmemcpy(&g_player[pbuf[1]].ps->pos.x, &pbuf[2], sizeof(vec3_t) * 2);
        Bmemcpy(&sprite[g_player[pbuf[1]].ps->i], &pbuf[2], sizeof(vec3_t));

        break;

    case PACKET_PLAYER_PING:
        g_player[0].pingcnt++;
        return;

    case PACKET_FRAG:
        if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) break;
        g_player[pbuf[1]].ps->frag_ps = pbuf[2];
        actor[g_player[pbuf[1]].ps->i].picnum = pbuf[3];
        ticrandomseed = B_UNBUF32(&pbuf[4]);
        P_FragPlayer(pbuf[1]);
        break;

        // [75]
    case PACKET_WORLD_UPDATE:
        Net_RecieveWorldUpdate(pbuf, packbufleng);
        break;


    default:
        Net_ParsePacketCommon(pbuf, packbufleng, 1);
        break;
    }
}

void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpacketp)
{
    switch (pbuf[0])
    {
    case PACKET_MESSAGE:
        Net_ReceiveMessage(pbuf, packbufleng);
        break;

    case PACKET_CLIENT_INFO:
        Net_ReceiveClientInfo(pbuf, packbufleng, serverpacketp);
        break;

    case PACKET_RTS:
        G_StartRTS(pbuf[1], 0);
        break;

    case PACKET_USER_MAP:
        Net_ReceiveUserMapName(pbuf, packbufleng);
        break;

    case PACKET_MAP_VOTE:
        Net_ReceiveMapVote(pbuf);
        break;

    case PACKET_MAP_VOTE_INITIATE: // call map vote
        Net_ReceiveMapVoteInitiate(pbuf);
        break;

    case PACKET_MAP_VOTE_CANCEL: // cancel map vote
        Net_ReceiveMapVoteCancel(pbuf);
        break;
    }
}

////////////////////////////////////////////////////////////////////////////////
// Acknowledgement Packets

void Net_SendAcknowledge(ENetPeer *client)
{
    if (!g_netServer)
        return;

    tempnetbuf[0] = PACKET_ACK;
    tempnetbuf[1] = myconnectindex;

    enet_peer_send(client, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));

    Dbg_PacketSent(PACKET_ACK);
}

void Net_ReceiveAcknowledge(uint8_t *pbuf, int32_t packbufleng)
{
    UNREFERENCED_PARAMETER(pbuf); // remove when this variable is used
    UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used

    Net_SendChallenge();
}

////////////////////////////////////////////////////////////////////////////////
// Challenge Packets

// sends the version and a simple crc32 of the current password, all verified by the server before the connection can continue
void Net_SendChallenge()
{
    if (!g_netClientPeer)
    {
        return;
    }

    tempnetbuf[0] = PACKET_AUTH;
    B_BUF16(&tempnetbuf[1], BYTEVERSION);
    B_BUF16(&tempnetbuf[3], NETVERSION);
    B_BUF32(&tempnetbuf[5], Bcrc32((uint8_t *)g_netPassword, Bstrlen(g_netPassword), 0));
    tempnetbuf[9] = myconnectindex;

    enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 10, ENET_PACKET_FLAG_RELIABLE));

    Dbg_PacketSent(PACKET_AUTH);
}

void Net_ReceiveChallenge(uint8_t *pbuf, int32_t packbufleng, ENetEvent *event)
{
    const uint16_t byteVersion = B_UNBUF16(&pbuf[1]);
    const uint16_t netVersion = B_UNBUF16(&pbuf[3]);
    const uint32_t crc = B_UNBUF32(&pbuf[5]);

    UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used

    if (byteVersion != BYTEVERSION || netVersion != NETVERSION)
    {
        enet_peer_disconnect_later(event->peer, DISC_VERSION_MISMATCH);
        initprintf("Bad client protocol: version %u.%u\n", byteVersion, netVersion);
        return;
    }
    if (crc != Bcrc32((uint8_t *)g_netPassword, Bstrlen(g_netPassword), 0))
    {
        enet_peer_disconnect_later(event->peer, DISC_BAD_PASSWORD);
        initprintf("Bad password from client.\n");
        return;
    }

    Net_SyncPlayer(event);
}

////////////////////////////////////////////////////////////////////////////////
// Num Players Packets

void Net_SendNewPlayer(int32_t newplayerindex)
{
    packbuf[0] = PACKET_NUM_PLAYERS;
    packbuf[1] = numplayers;
    packbuf[2] = g_mostConcurrentPlayers;
    packbuf[3] = ud.multimode;
    packbuf[4] = newplayerindex;
    packbuf[5] = g_networkMode;
    packbuf[6] = myconnectindex;
    enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 7, ENET_PACKET_FLAG_RELIABLE));

    Dbg_PacketSent(PACKET_NUM_PLAYERS);
}

void Net_ReceiveNewPlayer(uint8_t *pbuf, int32_t packbufleng)
{
    int32_t i;

    UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used

    numplayers = pbuf[1];
    g_mostConcurrentPlayers = pbuf[2];
    ud.multimode = pbuf[3];
    if (pbuf[4]) // ID of new player
    {
        g_player[pbuf[4]].playerquitflag = 1;

        if (!g_player[pbuf[4]].ps)
        {
            g_player[pbuf[4]].ps = (DukePlayer_t *)Xcalloc(1, sizeof(DukePlayer_t));
        }
        if (!g_player[pbuf[4]].inputBits)
        {
            g_player[pbuf[4]].inputBits = (input_t *)Xcalloc(1, sizeof(input_t));
        }
    }

    if (pbuf[5] == NET_DEDICATED_SERVER)
    {
        g_networkMode = NET_DEDICATED_CLIENT;
    }

    for (i = 0; i < g_mostConcurrentPlayers - 1; i++)
    {
        connectpoint2[i] = i + 1;
    }

    connectpoint2[g_mostConcurrentPlayers - 1] = -1;

    S_PlaySound(DUKE_GETWEAPON2);

    // myconnectindex is 0 until we get PACKET_PLAYER_INDEX
    if (myconnectindex != 0)
    {
        Net_SendClientInfo();
    }
}

////////////////////////////////////////////////////////////////////////////////
// Player Index Packets

void Net_SendPlayerIndex(int32_t index, ENetPeer *peer)
{
    packbuf[0] = PACKET_PLAYER_INDEX;
    packbuf[1] = index;
    packbuf[2] = myconnectindex;
    enet_peer_send(peer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 3, ENET_PACKET_FLAG_RELIABLE));

    Dbg_PacketSent(PACKET_PLAYER_INDEX);
}

void Net_ReceivePlayerIndex(uint8_t *pbuf, int32_t packbufleng)
{
    UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used

    myconnectindex = pbuf[1];
    g_player[myconnectindex].playerquitflag = 1;
    Net_SendClientInfo();
}

////////////////////////////////////////////////////////////////////////////////
// Client Info Packets

void Net_SendClientInfo(void)
{
    int32_t i, l;

    for (l = 0; (unsigned)l<sizeof(szPlayerName) - 1; l++)
        g_player[myconnectindex].user_name[l] = szPlayerName[l];

    if (numplayers < 2) return;

    tempnetbuf[0] = PACKET_CLIENT_INFO;
    l = 1;

    //null terminated player name to send
    for (i = 0; szPlayerName[i]; i++)
    {
        tempnetbuf[l++] = szPlayerName[i];
    }
    tempnetbuf[l++] = 0;

    tempnetbuf[l++] = g_player[myconnectindex].ps->aim_mode = ud.mouseaiming;
    tempnetbuf[l++] = g_player[myconnectindex].ps->auto_aim = ud.config.AutoAim;
    tempnetbuf[l++] = g_player[myconnectindex].ps->weaponswitch = ud.weaponswitch;
    tempnetbuf[l++] = g_player[myconnectindex].ps->palookup = g_player[myconnectindex].pcolor = ud.color;

    tempnetbuf[l++] = g_player[myconnectindex].pteam = ud.team;

    for (i = 0; i<10; i++)
    {
        g_player[myconnectindex].wchoice[i] = g_player[0].wchoice[i];
        tempnetbuf[l++] = (uint8_t)g_player[0].wchoice[i];
    }

    tempnetbuf[l++] = myconnectindex;

    Dbg_PacketSent(PACKET_CLIENT_INFO);

    if (g_netClient)
    {
        enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE));
    }
    else if (g_netServer)
    {
        enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE));
    }
}

void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver)
{
    uint32_t i, j;
    int32_t other = pbuf[packbufleng];

    for (i = 1; pbuf[i]; i++)
    {
        g_player[other].user_name[i - 1] = pbuf[i];
    }

    g_player[other].user_name[i - 1] = 0;
    i++;

    g_player[other].ps->aim_mode = pbuf[i++];
    g_player[other].ps->auto_aim = pbuf[i++];
    g_player[other].ps->weaponswitch = pbuf[i++];
    g_player[other].ps->palookup = g_player[other].pcolor = pbuf[i++];
    g_player[other].pteam = pbuf[i++];

    for (j = i; i - j<10; i++)
    {
        g_player[other].wchoice[i - j] = pbuf[i];
    }

    if (fromserver)
    {
        g_player[other].playerquitflag = 1;
    }
}

////////////////////////////////////////////////////////////////////////////////
// Map Name Packets

void Net_SendUserMapName(void)
{
    int32_t j;

    if (numplayers < 2)
        return;

    packbuf[0] = PACKET_USER_MAP;

    Bcorrectfilename(boardfilename, 0);

    // user map name is sent with a NUL at the end

    j = Bstrlen(boardfilename) + 1;
    Bmemcpy(&packbuf[1], boardfilename, j);
    j++;

    packbuf[j++] = myconnectindex;

    Dbg_PacketSent(PACKET_USER_MAP);

    if (g_netClient)
    {
        enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], j, ENET_PACKET_FLAG_RELIABLE));
    }
    else if (g_netServer)
    {
        enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], j, ENET_PACKET_FLAG_RELIABLE));
    }
}

void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng)
{
    int32_t i;

    NET_75_CHECK++; // possible buffer bug here on a bad client? if the file name didn't end in '\0', a hacker could "forget" to put '\0' at the end.

    Bstrcpy(boardfilename, (char *)pbuf + 1);
    boardfilename[packbufleng - 1] = 0;
    Bcorrectfilename(boardfilename, 0);
    if (boardfilename[0] != 0)
    {
        if ((i = kopen4loadfrommod(boardfilename, 0)) < 0)
        {
            Bmemset(boardfilename, 0, sizeof(boardfilename));
            Net_SendUserMapName();
        }
        else kclose(i);
    }

    if (ud.m_level_number == 7 && ud.m_volume_number == 0 && boardfilename[0] == 0)
        ud.m_level_number = 0;
}

////////////////////////////////////////////////////////////////////////////////
// Map Update Packets


//Insert a sprite from STAT_NETALLOC
int32_t Net_DoInsertSprite(int32_t sect, int32_t stat)
{
    int32_t i = headspritestat[STAT_NETALLOC];

    // This means that we've run out of server-side actors
    if (i < 0)
    {
        Net_Error_Disconnect("Out of server side actors");
        return i;
    }

    changespritestat(i, stat);
    do_insertsprite_at_headofsect(i, sect);

    Test_Iterate();

    return i;
}

//Insert a sprite from STAT_NETALLOC, and add it to the scratch pad list if we're a client.
int32_t Net_InsertSprite(int32_t sect, int32_t stat)
{
    int32_t i = Net_DoInsertSprite(sect, stat);

    if (i < 0)
    {
        return i;
    }

    if (g_netClient)
    {
        Net_InsertScratchPadSprite(i);
    }

    return i;
}

void Net_DeleteSprite(int32_t spritenum)
{
    // [75] later on, real clientside sprites (not scratchpad or server side) can be just deleted
    //      using deletesprite without worrying about linked lists or sprite indexes going out of sync.

    // [75] for most cases, only the server deletes sprites, clients just set their sprites invisible
    if (g_netClient)
    {
        sprite[spritenum].cstat = 32768;
        return;
    }
    
    Net_DoDeleteSprite(spritenum);

}



extern void Gv_RefreshPointers(void);

////////////////////////////////////////////////////////////////////////////////
// Player Updates

void Net_FillPlayerUpdate(playerupdate_t *update, int32_t player)
{
    update->playerindex = player;

    update->pos = g_player[player].ps->pos;
    update->opos = g_player[player].ps->opos;
    update->vel = g_player[player].ps->vel;
    update->ang = g_player[player].ps->ang;
    update->horiz = g_player[player].ps->horiz;
    update->horizoff = g_player[player].ps->horizoff;
    update->ping = g_player[player].ping;
    update->deadflag = g_player[player].ps->dead_flag;
    update->playerquitflag = g_player[player].playerquitflag;
}

void Net_ExtractPlayerUpdate(playerupdate_t *update, int32_t type)
{
    const int32_t playerindex = update->playerindex;

    if (playerindex != myconnectindex)
    {
        g_player[playerindex].ps->pos = update->pos;
        g_player[playerindex].ps->opos = update->opos;
        g_player[playerindex].ps->vel = update->vel;
        g_player[playerindex].ps->ang = update->ang;
        g_player[playerindex].ps->horiz = update->horiz;
        g_player[playerindex].ps->horizoff = update->horizoff;
    }

    if (type == PACKET_MASTER_TO_SLAVE)
    {
        g_player[playerindex].ping = update->ping;
        g_player[playerindex].ps->dead_flag = update->deadflag;
        g_player[playerindex].playerquitflag = update->playerquitflag;
    }



    //updatesectorz(g_player[other].ps->pos.x, g_player[other].ps->pos.y, g_player[other].ps->pos.z, &g_player[other].ps->cursectnum);
    //changespritesect(g_player[other].ps->i, g_player[other].ps->cursectnum);
}

////////////////////////////////////////////////////////////////////////////////
// Server Update Packets

#pragma pack(push,1)
typedef struct serverupdate_s
{
    uint8_t header;
    uint8_t numplayers;
    input_t nsyn;
    int32_t seed;
    int16_t pause_on;
} serverupdate_t;
#pragma pack(pop)

#pragma pack(push,1)
typedef struct serverplayerupdate_s
{
    uint8_t extra;
    int16_t cstat;
    uint16_t owner;
    uint16_t picnum;
    uint16_t gotweapon;
    uint8_t kickback_pic;
    uint8_t frags[MAXPLAYERS];
    int16_t inv_amount[GET_MAX];
    int16_t ammo_amount[MAX_WEAPONS];

    uint8_t curr_weapon;
    uint8_t last_weapon;
    uint8_t wantweaponfire;
    uint8_t weapon_pos;
    uint8_t frag_ps;
    uint8_t frag;
    uint8_t fraggedself;
    uint8_t last_extra;
    uint8_t pal;
    uint16_t ping;
    uint16_t newowner;

    playerupdate_t player;
} serverplayerupdate_t;
#pragma pack(pop)

// Send various player related updates (server -> client)
void Net_SendServerUpdates(void)
{
    int16_t i;
    uint8_t *updatebuf;
    serverupdate_t serverupdate;
    serverplayerupdate_t playerupdate;
    input_t *osyn = (input_t *)&inputfifo[1][0];
    input_t *nsyn = (input_t *)&inputfifo[0][0];

    ticrandomseed = randomseed;

    if (g_netServer)
    {
        Bmemcpy(&osyn[0], &nsyn[0], sizeof(input_t));
    }

    if (!g_netServer || numplayers < 2)
    {
        return;
    }

    serverupdate.header = PACKET_MASTER_TO_SLAVE;
    serverupdate.seed = ticrandomseed;
    serverupdate.nsyn = *nsyn;
    serverupdate.pause_on = ud.pause_on;

    serverupdate.numplayers = 0;
    updatebuf = tempnetbuf + sizeof(serverupdate_t);

    for (TRAVERSE_CONNECT(i))
    {
        if (g_player[i].playerquitflag == 0)
        {
            continue;
        }

        Net_FillPlayerUpdate(&playerupdate.player, i);

        playerupdate.gotweapon = g_player[i].ps->gotweapon;
        playerupdate.kickback_pic = g_player[i].ps->kickback_pic;
        Bmemcpy(playerupdate.frags, g_player[i].frags, sizeof(playerupdate.frags));
        Bmemcpy(playerupdate.inv_amount, g_player[i].ps->inv_amount, sizeof(playerupdate.inv_amount));
        Bmemcpy(playerupdate.ammo_amount, g_player[i].ps->ammo_amount, sizeof(playerupdate.ammo_amount));

        playerupdate.curr_weapon = g_player[i].ps->curr_weapon;
        playerupdate.last_weapon = g_player[i].ps->last_weapon;
        playerupdate.wantweaponfire = g_player[i].ps->wantweaponfire;
        playerupdate.weapon_pos = g_player[i].ps->weapon_pos;
        playerupdate.frag_ps = g_player[i].ps->frag_ps;
        playerupdate.frag = g_player[i].ps->frag;
        playerupdate.fraggedself = g_player[i].ps->fraggedself;
        playerupdate.last_extra = g_player[i].ps->last_extra;
        playerupdate.ping = g_player[i].ping;
        playerupdate.newowner = g_player[i].ps->newowner;

        Bmemcpy(updatebuf, &playerupdate, sizeof(serverplayerupdate_t));
        updatebuf += sizeof(serverplayerupdate_t);
        serverupdate.numplayers++;
    }

    if (serverupdate.numplayers == 0)
    {
        return;
    }

    Bmemcpy(tempnetbuf, &serverupdate, sizeof(serverupdate_t));

    enet_host_broadcast(g_netServer, CHAN_MOVE, enet_packet_create(&tempnetbuf[0], sizeof(serverupdate_t) + (serverupdate.numplayers * sizeof(serverplayerupdate_t)), 0));

    Dbg_PacketSent(PACKET_MASTER_TO_SLAVE);
}

void Net_ReceiveServerUpdate(ENetEvent *event)
{
    int32_t i;
    uint8_t *updatebuf;
    // int8_t numupdates;
    serverupdate_t serverupdate;
    serverplayerupdate_t playerupdate;

    if (((event->packet->dataLength - sizeof(serverupdate_t)) % sizeof(serverplayerupdate_t)) != 0)
    {
        return;
    }

    updatebuf = (uint8_t *)event->packet->data;
    Bmemcpy(&serverupdate, updatebuf, sizeof(serverupdate_t));
    updatebuf += sizeof(serverupdate_t);
    inputfifo[0][0] = serverupdate.nsyn;
    ud.pause_on = serverupdate.pause_on;

    ticrandomseed = serverupdate.seed;

    for (i = 0; i < serverupdate.numplayers; ++i)
    {
        Bmemcpy(&playerupdate, updatebuf, sizeof(serverplayerupdate_t));
        updatebuf += sizeof(serverplayerupdate_t);

        Net_ExtractPlayerUpdate(&playerupdate.player, PACKET_MASTER_TO_SLAVE);

        g_player[i].ps->gotweapon = playerupdate.gotweapon;

        g_player[i].ps->kickback_pic = playerupdate.kickback_pic;

        Bmemcpy(g_player[i].frags, playerupdate.frags, sizeof(playerupdate.frags));
        Bmemcpy(g_player[i].ps->inv_amount, playerupdate.inv_amount, sizeof(playerupdate.inv_amount));
        Bmemcpy(g_player[i].ps->ammo_amount, playerupdate.ammo_amount, sizeof(playerupdate.ammo_amount));

        g_player[i].ps->curr_weapon = playerupdate.curr_weapon;
        g_player[i].ps->last_weapon = playerupdate.last_weapon;
        g_player[i].ps->wantweaponfire = playerupdate.wantweaponfire;
        g_player[i].ps->weapon_pos = playerupdate.weapon_pos;
        g_player[i].ps->frag_ps = playerupdate.frag_ps;
        g_player[i].ps->frag = playerupdate.frag;
        g_player[i].ps->fraggedself = playerupdate.fraggedself;
        g_player[i].ps->last_extra = playerupdate.last_extra;
        g_player[i].ping = playerupdate.ping;



        g_player[i].ps->newowner = playerupdate.newowner;
    }
}

////////////////////////////////////////////////////////////////////////////////
// Client Update Packets

#pragma pack(push,1)
typedef struct clientupdate_s
{
    uint8_t header;
    int32_t RevisionNumber;
    input_t nsyn;
    playerupdate_t player;
} clientupdate_t;
#pragma pack(pop)

void Net_SendClientUpdate(void)
{
    clientupdate_t update;
    update.header = PACKET_SLAVE_TO_MASTER;
    update.RevisionNumber = g_netMapRevisionNumber;
    update.nsyn = inputfifo[0][myconnectindex];

    Net_FillPlayerUpdate(&update.player, myconnectindex);

    enet_peer_send(g_netClientPeer, CHAN_MOVE, enet_packet_create(&update, sizeof(clientupdate_t), 0));

    Dbg_PacketSent(PACKET_SLAVE_TO_MASTER);
}

//Server only
void Net_ReceiveClientUpdate(ENetEvent *event)
{
    int32_t playeridx;
    clientupdate_t update;

    if (event->packet->dataLength != sizeof(clientupdate_t))
    {
        return;
    }

    Bmemcpy(&update, (char *)event->packet->data, sizeof(clientupdate_t));

    playeridx = (int32_t)(intptr_t)event->peer->data;

    if (playeridx < 0 || playeridx >= MAXPLAYERS)
    {
        return;
    }

    g_player[playeridx].revision = update.RevisionNumber;
    inputfifo[0][playeridx] = update.nsyn;

    Net_ExtractPlayerUpdate(&update.player, PACKET_SLAVE_TO_MASTER);
}

////////////////////////////////////////////////////////////////////////////////
// Message Packets

void Net_SendMessage(void)
{

    if (g_player[myconnectindex].ps->gm&MODE_SENDTOWHOM)
    {
        int32_t i, j;
        if (g_chatPlayer != -1 || ud.multimode < 3)
        {
            tempbuf[0] = PACKET_MESSAGE;
            tempbuf[2] = 0;
            recbuf[0]  = 0;

            if (ud.multimode < 3)
                g_chatPlayer = 2;

            if (typebuf[0] == '/' && Btoupper(typebuf[1]) == 'M' && Btoupper(typebuf[2]) == 'E')
            {
                Bstrcat(recbuf,"* ");
                i = 3, j = Bstrlen(typebuf);
                Bstrcpy(tempbuf,typebuf);
                while (i < j)
                {
                    typebuf[i-3] = tempbuf[i];
                    i++;
                }
                typebuf[i-3] = '\0';
                Bstrcat(recbuf,g_player[myconnectindex].user_name);
            }
            else
            {
                Bstrcat(recbuf,g_player[myconnectindex].user_name);
                Bstrcat(recbuf,": ");
            }

            Bstrcat(recbuf,"^00");
            Bstrcat(recbuf,typebuf);
            j = Bstrlen(recbuf);
            recbuf[j] = 0;
            Bstrcat(tempbuf+2,recbuf);

            if (g_chatPlayer >= ud.multimode)
            {
                tempbuf[1] = 255;
                tempbuf[j+2] = myconnectindex;
                j++;
                if (g_netServer) enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(&tempbuf[0], j+2, 0));
                else if (g_netClient) enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(&tempbuf[0], j+2, 0));
                G_AddUserQuote(recbuf);
            }
            g_chatPlayer = -1;
            g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM);
        }
        else if (g_chatPlayer == -1)
        {
            j = 50;
            gametext_center(j, "Send message to...");
            j += 8;
            for (TRAVERSE_CONNECT(i))
            {
                if (i == myconnectindex)
                {
                    minitextshade((320>>1)-40+1,j+1,"A/ENTER - ALL",26,0,2+8+16);
                    minitext((320>>1)-40,j,"A/ENTER - ALL",0,2+8+16);
                    j += 7;
                }
                else
                {
                    Bsprintf(recbuf,"      %d - %s",i+1,g_player[i].user_name);
                    minitextshade((320>>1)-40-6+1,j+1,recbuf,26,0,2+8+16);
                    minitext((320>>1)-40-6,j,recbuf,0,2+8+16);
                    j += 7;
                }
            }
            minitextshade((320>>1)-40-4+1,j+1,"    ESC - Abort",26,0,2+8+16);
            minitext((320>>1)-40-4,j,"    ESC - Abort",0,2+8+16);
            j += 7;

            mpgametext(mpgametext_x, ud.screen_size > 0 ? (200-45)<<16 : (200-8)<<16, typebuf, 0, 0, 0, 0);

            if (KB_KeyWaiting())
            {
                i = KB_GetCh();

                if (i == 'A' || i == 'a' || i == 13)
                    g_chatPlayer = ud.multimode;
                else if (i >= '1' || i <= (ud.multimode + '1'))
                    g_chatPlayer = i - '1';
                else
                {
                    g_chatPlayer = ud.multimode;
                    if (i == 27)
                    {
                        g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM);
                        g_chatPlayer = -1;
                    }
                    else
                        typebuf[0] = 0;
                }

                KB_ClearKeyDown(sc_1);
                KB_ClearKeyDown(sc_2);
                KB_ClearKeyDown(sc_3);
                KB_ClearKeyDown(sc_4);
                KB_ClearKeyDown(sc_5);
                KB_ClearKeyDown(sc_6);
                KB_ClearKeyDown(sc_7);
                KB_ClearKeyDown(sc_8);
                KB_ClearKeyDown(sc_A);
                KB_ClearKeyDown(sc_Escape);
                KB_ClearKeyDown(sc_Enter);
            }
        }
    }
    else
    {
        int32_t const hitstate = I_EnterText(typebuf, 120, 0);

        int32_t const y = ud.screen_size > 1 ? (200-58)<<16 : (200-35)<<16;

        int32_t const width = mpgametextsize(typebuf, TEXT_LITERALESCAPE).x;
        int32_t const fullwidth = width + textsc((tilesiz[SPINNINGNUKEICON].x<<15)+(2<<16));
        int32_t const text_x = fullwidth >= (320<<16) ? (320<<16) - fullwidth : mpgametext_x;
        mpgametext(text_x, y, typebuf, 1, 2|8|16|ROTATESPRITE_FULL16, 0, TEXT_YCENTER|TEXT_LITERALESCAPE);
        int32_t const cursor_x = text_x + width + textsc((tilesiz[SPINNINGNUKEICON].x<<14)+(1<<16));
        rotatesprite_fs(cursor_x, y, textsc(32768), 0, SPINNINGNUKEICON+((totalclock>>3)%7), 4-(sintable[(totalclock<<4)&2047]>>11), 0, 2|8);

        if (hitstate == 1)
        {
            KB_ClearKeyDown(sc_Enter);
            if (Bstrlen(typebuf) == 0)
            {
                g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM);
                return;
            }
            if (ud.automsg)
            {
                if (SHIFTS_IS_PRESSED) g_chatPlayer = -1;
                else g_chatPlayer = ud.multimode;
            }
            g_player[myconnectindex].ps->gm |= MODE_SENDTOWHOM;
        }
        else if (hitstate == -1)
            g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM);
        else pub = NUMPAGES;
    }
}

void Net_ReceiveMessage(uint8_t *pbuf, int32_t packbufleng)
{
    Bstrncpy(recbuf, (char *)pbuf + 2, packbufleng - 2);
    recbuf[packbufleng - 2] = 0;

    G_AddUserQuote(recbuf);
    S_PlaySound(EXITMENUSOUND);

    pus = pub = NUMPAGES;
}

////////////////////////////////////////////////////////////////////////////////
// New Game Packets

// set all actors, walls, and sectors in a snapshot to their Null states.
void Net_InitMapState(netmapstate_t* mapState)
{
    netactor_t  sprDefault = cNullNetActor;

    sprDefault.spr_statnum = MAXSTATUS;
    sprDefault.spr_sectnum = MAXSECTORS;

    int32_t     index = 0;

    mapState->maxActorIndex = 0;

    // this code is just a (slow) memset as it is now, 
    // it may be a good idea to use "baselines", which can reduce the amount
    // of delta encoding when a sprite is first added. This
    // could be a good optimization to consider later.
    //
    // note that if we actually do that, NullMapState needs to be 
    // passed through this function!
    for (index = 0; index < MAXSPRITES; index++)
    {
        mapState->actor[index] = sprDefault;
        mapState->actor[index].netIndex = index;
    }

    for (index = 0; index < MAXWALLS; index++)
    {
        mapState->wall[index] = cNullNetWall;
        mapState->wall[index].netIndex = index;
    }

    for (index = 0; index < MAXSECTORS; index++)
    {
        mapState->sector[index] = cNullNetSector;
        mapState->sector[index].netIndex = index;
    }

    mapState->revisionIndex = 0x86753095;

}

void Net_InitMapStateHistory()
{
    int32_t mapStateIndex = 0;

    for (mapStateIndex = 0; mapStateIndex < NET_REVISIONS; mapStateIndex++)
    {
        netmapstate_t* mapState = &g_mapStateHistory[mapStateIndex];

        Net_InitMapState(mapState);

    }

    Net_InitMapState(&NullMapState);
    NullMapState.revisionIndex = cNoMapStateRevisionNumber;

    Net_InitMapState(&g_mapStartState);
    g_mapStartState.revisionIndex = cMapStartStateRevisionNumber;

    g_netMapRevisionNumber = cNoMapStateRevisionNumber;

}

// Both client and server execute this
static void Net_ResetPlayers()
{
    int32_t playerIndex;

    for (TRAVERSE_CONNECT(playerIndex))
    {
        P_ResetWeapons(playerIndex);
        P_ResetInventory(playerIndex);
        g_player[playerIndex].revision = cNoMapStateRevisionNumber;
    }

}

void Net_StartNewGame()
{
    Net_ResetPlayers();

    Net_ExtractNewGame(&pendingnewgame, 0);
    G_NewGame(ud.volume_number, ud.level_number, ud.player_skill);
    ud.coop = ud.m_coop;

    if (G_EnterLevel(MODE_GAME))
    {
        G_BackToMenu();
    }
}

void Net_NotifyNewGame()
{
    int32_t spriteIndex;
    int32_t statIndex;
    int32_t numSprites = 0;
    int32_t numSpritesToNetAlloc = 0;

    if (!g_netServer && !g_netClient)
    {
        return;
    }

    // clients do NOT need to do this, they'll get it from the server's initial snapshot.
    if (g_netServer)
    {
        // Grab the total number of sprites at level load
        for (statIndex = 0; statIndex < MAXSTATUS; ++statIndex)
        {
            spriteIndex = headspritestat[statIndex];
            for (; spriteIndex >= 0; spriteIndex = nextspritestat[spriteIndex])
            {
                numSprites++;
            }
        }


        // Take half of the leftover sprites and allocate them for the network's nefarious purposes.
        numSpritesToNetAlloc = (MAXSPRITES - numSprites) / 2;
        for (spriteIndex = 0; spriteIndex < numSpritesToNetAlloc; ++spriteIndex)
        {
            int32_t newSprite = insertspritestat(STAT_NETALLOC);
            sprite[newSprite].sectnum = MAXSECTORS;
            Numsprites++;
        }
    }

    //[75] Note:    DON'T set the initial map state or initialize the map state history in the packet code,
    //              The client didn't load the map until G_EnterLevel

}

void Net_ResetPlayerReady()
{
    int32_t playerindex;

    for (TRAVERSE_CONNECT(playerindex))
    {
        g_player[playerindex].ready = 0;
    }
}

int8_t Net_AllPlayersReady()
{
    int32_t playerindex;

    for (TRAVERSE_CONNECT(playerindex))
    {
        if (!(g_player[playerindex].ready))
        {
            return 0;
        }
    }

    return 1;
}

void Net_SendNewGame(int32_t frommenu, ENetPeer *peer)
{
    newgame_t newgame;

    newgame.header = PACKET_NEW_GAME;
    Net_FillNewGame(&newgame, frommenu);

    Dbg_PacketSent(PACKET_NEW_GAME);

    if (peer)
    {
        enet_peer_send(peer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE));
    }
    else
    {
        enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE));
    }

    Net_ResetPlayerReady();
}

// client only
void Net_ReceiveNewGame(ENetEvent *event)
{
    ClientPlayerReady = 0;

    if ((vote_map + vote_episode + voting) != -3)
        G_AddUserQuote("Vote Succeeded");

    Bmemcpy(&pendingnewgame, event->packet->data, sizeof(newgame_t));
    Net_StartNewGame();

    packbuf[0] = PACKET_PLAYER_READY;
    packbuf[1] = myconnectindex;

    if (g_netClientPeer)
    {
        enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
        Dbg_PacketSent(PACKET_PLAYER_READY);

        if (g_netClient)
        {
            ClientPlayerReady = 1;
        }
    }

    //[75] Note:    DON'T set the initial map state or initialize the map state history in the packet code,
    //              The client didn't load the map until G_EnterLevel

    g_player[myconnectindex].ps->gm = MODE_GAME;
    ready2send = 1;
}

void Net_FillNewGame(newgame_t *newgame, int32_t frommenu)
{
    if (frommenu)
    {
        newgame->level_number = ud.m_level_number;
        newgame->volume_number = ud.m_volume_number;
        newgame->player_skill = ud.m_player_skill;
        newgame->monsters_off = ud.m_monsters_off;
        newgame->respawn_monsters = ud.m_respawn_monsters;
        newgame->respawn_items = ud.m_respawn_items;
        newgame->respawn_inventory = ud.m_respawn_inventory;
        newgame->ffire = ud.m_ffire;
        newgame->noexits = ud.m_noexits;
        newgame->coop = ud.m_coop;
    }
    else
    {
        newgame->level_number = ud.level_number;
        newgame->volume_number = ud.volume_number;
        newgame->player_skill = ud.player_skill;
        newgame->monsters_off = ud.monsters_off;
        newgame->respawn_monsters = ud.respawn_monsters;
        newgame->respawn_items = ud.respawn_items;
        newgame->respawn_inventory = ud.respawn_inventory;
        newgame->ffire = ud.ffire;
        newgame->noexits = ud.noexits;
        newgame->coop = ud.coop;
    }
}

void Net_ExtractNewGame(newgame_t *newgame, int32_t menuonly)
{
    ud.m_level_number = newgame->level_number;
    ud.m_volume_number = newgame->volume_number;
    ud.m_player_skill = newgame->player_skill;
    ud.m_monsters_off = newgame->monsters_off;
    ud.m_respawn_monsters = newgame->respawn_monsters;
    ud.m_respawn_items = newgame->respawn_items;
    ud.m_respawn_inventory = newgame->respawn_inventory;
    ud.m_ffire = newgame->ffire;
    ud.m_noexits = newgame->noexits;
    ud.m_coop = newgame->coop;

    if (!menuonly)
    {
        ud.level_number = newgame->level_number;
        ud.volume_number = newgame->volume_number;
        ud.player_skill = newgame->player_skill;
        ud.monsters_off = newgame->monsters_off;
        ud.respawn_monsters = newgame->respawn_monsters;
        ud.respawn_monsters = newgame->respawn_items;
        ud.respawn_inventory = newgame->respawn_inventory;
        ud.ffire = newgame->ffire;
        ud.noexits = newgame->noexits;
        ud.coop = newgame->coop;
    }
}

////////////////////////////////////////////////////////////////////////////////
// Map Vote Packets

void Net_SendMapVoteInitiate(void)
{
    newgame_t newgame;

    if (!g_netClient)
    {
        return;
    }

    voting = myconnectindex;

    newgame.header = PACKET_MAP_VOTE_INITIATE;
    newgame.connection = myconnectindex;
    Net_FillNewGame(&newgame, 1);

    Dbg_PacketSent(PACKET_MAP_VOTE_INITIATE);

    enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE));
}

void Net_ReceiveMapVoteInitiate(uint8_t *pbuf)
{
    int32_t playerIndex;

    Bmemcpy(&pendingnewgame, pbuf, sizeof(newgame_t));
    Net_ExtractNewGame(&pendingnewgame, 1);

    voting = pendingnewgame.connection;
    vote_episode = pendingnewgame.volume_number;
    vote_map = pendingnewgame.level_number;

    Bsprintf(tempbuf, "%s^00 has called a vote to change map to %s (E%dL%d)",
        g_player[voting].user_name,
        g_mapInfo[(uint8_t)(vote_episode*MAXLEVELS + vote_map)].name,
        vote_episode + 1, vote_map + 1);
    G_AddUserQuote(tempbuf);

    Bsprintf(tempbuf, "Press F1 to Accept, F2 to Decline");
    G_AddUserQuote(tempbuf);

    for (playerIndex = MAXPLAYERS - 1; playerIndex >= 0; playerIndex--)
    {
        g_player[playerIndex].vote = 0;
        g_player[playerIndex].gotvote = 0;
    }

    g_player[voting].gotvote = g_player[voting].vote = 1;
}

void Net_SendMapVote(int32_t votefor)
{
    voting = -1;
    g_player[myconnectindex].gotvote = 1;
    g_player[myconnectindex].vote = votefor;

    tempbuf[0] = PACKET_MAP_VOTE;
    tempbuf[1] = myconnectindex;
    tempbuf[2] = votefor;
    tempbuf[3] = myconnectindex;

    Dbg_PacketSent(PACKET_MAP_VOTE);

    if (g_netClient)
    {
        enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 4, ENET_PACKET_FLAG_RELIABLE));
    }
    else if (g_netServer)
    {
        enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 4, ENET_PACKET_FLAG_RELIABLE));
    }

    Net_CheckForEnoughVotes();
}

void Net_ReceiveMapVote(uint8_t *pbuf)
{
    if (voting == myconnectindex && g_player[(uint8_t)pbuf[1]].gotvote == 0)
    {
        Bsprintf(tempbuf, "Confirmed vote from %s", g_player[(uint8_t)pbuf[1]].user_name);
        G_AddUserQuote(tempbuf);
    }

    if (!g_netServer)
    {
        return;
    }

    g_player[(uint8_t)pbuf[1]].gotvote = 1;
    g_player[(uint8_t)pbuf[1]].vote = pbuf[2];
    Net_CheckForEnoughVotes();
}

void Net_CheckForEnoughVotes()
{
    int32_t i;
    int32_t requiredvotes;
    int32_t numfor, numagainst;

    // Only the server can decide map changes
    if (!g_netServer || numplayers <= 1)
    {
        return;
    }

    // If there are just two players, both of them deserve a vote
    if (numplayers == 2)
    {
        requiredvotes = 2;
    }
    else
    {
        // If more than two players, we need at least 50% of the players to vote
        // Which means that if there's an odd number of players, we'll need slightly more than 50% of the vote.
        requiredvotes = numplayers / 2;
        if (numplayers % 2 == 1)
        {
            requiredvotes++;
        }
    }

    numfor = numagainst = 0;
    for (i = 0; i<MAXPLAYERS; i++)
    {
        if (g_player[i].gotvote)
        {
            if (g_player[i].vote)
            {
                numfor++;
            }
            else
            {
                numagainst++;
            }
        }
    }

    if (numfor >= requiredvotes)
    {
        Net_StartNewGame();
        Net_SendNewGame(1, NULL); // map vote
    }
    else if (numagainst >= requiredvotes || (numfor + numagainst) == numplayers)
    {
        Net_SendMapVoteCancel(1);
    }
}

// If failed is true, that means the vote lost. Otherwise it was cancelled by the client who initiated it.
void Net_SendMapVoteCancel(int32_t failed)
{
    // Only the server or the client that initiated the vote can cancel the vote
    if (g_netClient && voting != myconnectindex)
    {
        return;
    }

    tempbuf[0] = PACKET_MAP_VOTE_CANCEL;
    tempbuf[1] = myconnectindex;

    // If we're forwarding a cancelled message, change the connection index to the one who cancelled it.
    if (g_netServer && !failed)
    {
        tempbuf[1] = voting;
    }

    voting = -1;

    Dbg_PacketSent(PACKET_MAP_VOTE_CANCEL);

    if (g_netClient)
    {
        enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
    }
    else if (g_netServer)
    {
        enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
    }
}

void Net_ReceiveMapVoteCancel(uint8_t *pbuf)
{
    // int32_t numvotes;

    // Ignore if we're not voting
    if (voting == -1)
    {
        return;
    }

    // Ignore cancellations from clients that did not initiate the map vote
    if (voting != pbuf[1] && voting != myconnectindex)
    {
        return;
    }

    if (voting == myconnectindex || voting != pbuf[1])
    {
        Bsprintf(tempbuf, "Vote Failed");
    }
    else if (voting == pbuf[1])
    {
        Bsprintf(tempbuf, "%s^00 has canceled the vote", g_player[voting].user_name);
    }

    G_AddUserQuote(tempbuf);

    if (g_netServer)
    {
        Net_SendMapVoteCancel(0);
    }

    voting = -1;
}

// [75]
//---------------------------------------------------------------------------------------------------------------------------------


// enforce signed/unsigned and overflow behavior for WriteBits smaller than 32
// remember that all of the WriteX functions only take 32 bit integers as input...
#define PICKY_TYPECHECK 1

#define MAX_PACKET_STRING 1024

#define LOG_MESSAGE_BITS 1

#define NetNumberOfIndexes (1 << NETINDEX_BITS) 
#define IndexesOK (MAXWALLS < NetNumberOfIndexes) && (MAXSPRITES < NetNumberOfIndexes) && (MAXSECTORS < NetNumberOfIndexes)

#if(!IndexesOK)
#error "net.c: game arrays are now too big to send over the network, please update NETINDEX_BITS to be the right length to store a wall, sprite, and sector index"
#endif

#define STRUCTINDEX_BITS 8 // hopefully no game structs ever have more than 255 fields....

#define DEBUG_CHANGEDINDEXES 1

#define	FLOAT_INT_BITS	13

// remember that the minimum negative number is the sign bit + all zeros
const int32_t cTruncInt_Min = -(1 << (FLOAT_INT_BITS - 1));
const int32_t cTruncInt_Max = (1 << (FLOAT_INT_BITS - 1)) - 1;

typedef struct NetBuffer_s
{
    uint8_t     *Data;
    int32_t     Bit;
    int32_t     ReadCurByte;
    int32_t     CurSize;
    int32_t     MaxSize; // set in NetBuffer_Init to the size of the byte array that this struct referrs to in Data
} NetBuffer_t;

// NOTE: does NOT fill iByteArray with zeros
void NetBuffer_Init(NetBuffer_t *buffer, uint8_t *byteArray, int32_t arrayLength)
{
    // fill all the entries of the netbuffer struct (reminder: the byte array is not part of the struct)
    Bmemset(buffer, 0, sizeof(*buffer));
    buffer->Data = byteArray;
    buffer->MaxSize = arrayLength;
}


///<summary>
///guarantees that the destination string will be nul terminated
///</summary>
void static SafeStrncpyz(char *dest, const char *src, int destsize)
{
    if (!dest || !src || (destsize <= 0))
    {

        Bassert(dest);
        Bassert(src);
        Bassert(destsize > 0);
        Net_Error_Disconnect("SafeStrncpyz: Invalid src, dest, or datasize.");
        return;
    }


    strncpy(dest, src, destsize - 1);
    dest[destsize - 1] = 0;
}

// Write a bit (bitValue) to a byte array (dataBuffer) at bit offset bitOffset,
// this function increments bitOffset for you.
static void PutBit(int32_t bitValue, uint8_t *dataBuffer, int32_t *bitOffset)
{
    int32_t bitOffsetValue = *bitOffset;

    const int32_t byteIndex = bitOffsetValue >> 3;
    const int32_t bitIndex = bitOffsetValue & 7;

    // reset this byte to 0 on the first bit
    if ((bitIndex) == 0)
    {
        dataBuffer[byteIndex] = 0;
    }

    dataBuffer[byteIndex] |= (bitValue << (bitIndex));


    bitOffsetValue++;
    *bitOffset = bitOffsetValue;
}

// Get a single bit (return value) from a byte array (dataBuffer) at bit offset bitOffset
// this function increments bitOffset for you.
static int32_t GetBit(uint8_t *dataBuffer, int32_t *bitOffset)
{
    int32_t bitValue = -1;

    int32_t bitOffsetValue = *bitOffset;

    const int32_t byteIndex = bitOffsetValue >> 3;
    const int32_t bitIndex = bitOffsetValue & 7;

    bitValue = (dataBuffer[byteIndex] >> bitIndex) & 0x1;

    bitOffsetValue++;
    *bitOffset = bitOffsetValue;

    return bitValue;
}

//Note: This function increments bitOffset for you
static void PutBits(int32_t value, uint8_t *dataBuffer, int32_t *bitOffset, int16_t numberOfBits)
{
    int16_t bitIndex;

    if (numberOfBits > 32)
    {
        Net_Error_Disconnect("PutBits: Attempted to write more than 32 bits to the buffer.");
        numberOfBits = 32;
    }

    for (bitIndex = 0; bitIndex < numberOfBits; bitIndex++)
    {
        int32_t bitValue = value & (1 << bitIndex);

        PutBit((bitValue != 0), dataBuffer, bitOffset);

    }

}

//Note: this function increments bitOffset for you
static int32_t GetBits(uint8_t *dataBuffer, int32_t *bitOffset, int16_t numberOfBits)
{
    int32_t value = 0;
    int16_t bitIndex;

    if (numberOfBits > 32)
    {
        Net_Error_Disconnect("GetBits: Attempted to read more than 32 bits from the buffer.");
        numberOfBits = 32;
    }

    for (bitIndex = 0; bitIndex < numberOfBits; bitIndex++)
    {
        int32_t tBitValue = GetBit(dataBuffer, bitOffset);

        value |= (tBitValue << bitIndex);

    }

    return value;
}

// this is mostly just here to put a breakpoint on, use conditional breakpoints to stop only on particular types of packets
int32_t Dbg_PacketSent(enum DukePacket_t iPacketType)
{
    return iPacketType + 1;
}

// only difference between this and GetBits is this one does not require you
// to pass in a pointer for the bit offset. Remember to not use semicolons in the watch list!
//
// use this to debug packets
//
// NOTE: Set to non-static just so the compiler won't optimize it out.
int32_t Dbg_GetBits(NetBuffer_t *netBuffer, int32_t bitOffset, int16_t numberOfBits)
{
    // I'm having GetBits modify a temp instead of the input parameter so that you can
    // still see what the input parameter was at the end of the function.
    int32_t bitOffsetValue = bitOffset;
    return GetBits(netBuffer->Data, &bitOffsetValue, numberOfBits);
}

// intended for use in the debugger
// the netcode will return (32 bit) floats in a variable with type int32, though the data is actually
// floating point data. This function is here to make it easier to verify a (full) float encoded as int32
// in the debugger.
// note: floats that are actually truncated integers will usually look something like this: 3.922e-39#DEN
static float Dbg_RawIntToFloat(int32_t floatRawData)
{
    float* floatPtr = (float*)&floatRawData;

    return *(floatPtr);
}


void NetBuffer_WriteBits(NetBuffer_t *netBuffer, int32_t data, int16_t numberOfBits)
{
    if (netBuffer->CurSize >= netBuffer->MaxSize)
    {
        Net_Error_Disconnect("NetBuffer_WriteBits: Buffer overrun.");
    }


    int32_t dataToWrite = data & (0xffffffff >> (32 - numberOfBits));

    PutBits(dataToWrite, netBuffer->Data, &netBuffer->Bit, numberOfBits);

    netBuffer->CurSize = (netBuffer->Bit >> 3) + 1;

}

void NetBuffer_WriteUnsignedByte(NetBuffer_t *netBuffer, int32_t byte)
{
#ifdef PICKY_TYPECHECK
    if ((byte < 0) || byte > 255)
    {
        Net_Error_Disconnect("NetBuffer_WriteUnsignedByte: Value not valid.");
    }
#endif
    NetBuffer_WriteBits(netBuffer, byte, 8);
}

void NetBuffer_WriteSignedByte(NetBuffer_t *netBuffer, int32_t byte)
{
#ifdef PICKY_TYPECHECK
    if ((byte < -128) || (byte > 127))
    {
        Net_Error_Disconnect("NetBuffer_WriteSignedByte: Value not valid.");
    }
#endif
    NetBuffer_WriteBits(netBuffer, byte, 8);
}


void NetBuffer_WriteInt16(NetBuffer_t *netBuffer, int32_t word)
{
#ifdef PICKY_TYPECHECK
    const int16_t cLowVal = (int16_t)0x00008000;
    const int16_t cHighVal = (int16_t)0x00007fff;

    if ((word < cLowVal) || (word > cHighVal))
    {
        Net_Error_Disconnect("NetBuffer_WriteInt16: Value not valid.");
    }
#endif
    NetBuffer_WriteBits(netBuffer, word, 16);
}

void NetBuffer_WriteUInt16(NetBuffer_t *netBuffer, int32_t word)
{
#ifdef PICKY_TYPECHECK
    const uint16_t cLowVal = (uint16_t)0x00000000;
    const uint16_t cHighVal = (uint16_t)0x0000ffff;

    if ((word < cLowVal) | (word > cHighVal))
    {
        Net_Error_Disconnect("NetBuffer_WriteUInt16: Value not valid.");
    }

#endif
    NetBuffer_WriteBits(netBuffer, word, 16);
}

void NetBuffer_WriteDword(NetBuffer_t *netBuffer, int32_t dword)
{
    // don't care about signed/unsigned since the bit width matches
    NetBuffer_WriteBits(netBuffer, dword, 32);
}


//where length is the size in bytes
void NetBuffer_WriteData(NetBuffer_t  *netBuffer, const void *data, int16_t length)
{
    int32_t byteIndex;
    for (byteIndex = 0; byteIndex < length; byteIndex++)
    {
        NetBuffer_WriteUnsignedByte(netBuffer, ((int8_t *)data)[byteIndex]);
    }
}

void NetBuffer_WriteString(NetBuffer_t *netBuffer, const char *stringToWrite)
{
    if (!stringToWrite)
    {
        // if stringToWrite is null, write a NUL ('\0') character
        NetBuffer_WriteData(netBuffer, "", 1);
    }

    else
    {
        int32_t     length; // length of input string
        char        tempStringBuffer[MAX_PACKET_STRING]; // temp storage

        length = strlen(stringToWrite);

        if (length >= MAX_PACKET_STRING)
        {
            Net_Error_Disconnect("NetBuffer_WriteString: String too long.");
        }
        SafeStrncpyz(tempStringBuffer, stringToWrite, sizeof(tempStringBuffer));

        NetBuffer_WriteData(netBuffer, tempStringBuffer, length + 1);
    }
}

void NetBuffer_WriteDeltaFloat(NetBuffer_t *netBuffer, const int32_t rawFloatData)
{
    if (sizeof(float) != sizeof(int32_t))
    {
        Net_Error_Disconnect("Can't write float to buffer: sizeof(float) must be 4 bytes");
    }


    // interpret the FOUR BYTES of raw float data as a float
    float*	floatDataPtr = (float*) &(rawFloatData);
    float	floatValue = *(floatDataPtr);

    int32_t truncatedFloat = (int32_t)floatValue;

    if (floatValue == 0.0f)
    {
        NetBuffer_WriteBits(netBuffer, 0, 1);                                          // float is zeroed

    }
    else
    {
        NetBuffer_WriteBits(netBuffer, 1, 1);                                          // float is not zeroed

                                                                                       // check if this float can be sent as a FLOAT_INT_BITS sized (*signed*) integer with no data loss
        if (
            ((float)truncatedFloat == floatValue) &&
            (truncatedFloat >= cTruncInt_Min) &&
            (truncatedFloat <= cTruncInt_Max)
            )
        {
            NetBuffer_WriteBits(netBuffer, 0, 1);                                                 // send float as small integer
            NetBuffer_WriteBits(netBuffer, truncatedFloat + cTruncInt_Max, FLOAT_INT_BITS);      // float as small integer data

        }

        else
        {
            // send as full floating point value

            NetBuffer_WriteBits(netBuffer, 1, 1);                                          // send full float
            NetBuffer_WriteBits(netBuffer, rawFloatData, 32);                              // full raw float data

        }

    }

}

//-------------------------------------------------------------------------------------
// low level buffer reading functions 

int32_t NetBuffer_ReadBits(NetBuffer_t *netBuffer, int32_t numberOfBits)
{
    int32_t	value = GetBits(netBuffer->Data, &netBuffer->Bit, numberOfBits);

    netBuffer->ReadCurByte = (netBuffer->Bit >> 3) + 1;

    return value;
}


// returns -1 if no more characters are available
int32_t NetBuffer_ReadByte(NetBuffer_t *netBuffer)
{
    int32_t	byte;

    byte = (int8_t)NetBuffer_ReadBits(netBuffer, 8);

    if (netBuffer->ReadCurByte > netBuffer->CurSize)
    {
        Net_Error_Disconnect("NetBuffer_ReadByte: Attempted to read beyond end of buffer current size.");
    }

    return byte;
}

int32_t NetBuffer_ReadUnsignedByte(NetBuffer_t *netBuffer)
{
    int32_t	byte;

    byte = (uint8_t)NetBuffer_ReadBits(netBuffer, 8);

    if (netBuffer->ReadCurByte > netBuffer->CurSize)
    {
        Net_Error_Disconnect("NetBuffer_ReadUnsignedByte: Attempted to read beyond end of buffer current size.");
    }

    return byte;
}

int32_t NetBuffer_ReadInt16(NetBuffer_t *netBuffer)
{
    int32_t	word;

    word = (int16_t)NetBuffer_ReadBits(netBuffer, 16);
    if (netBuffer->ReadCurByte > netBuffer->CurSize)
    {
        Net_Error_Disconnect("NetBuffer_ReadInt16: Attempted to read beyond end of buffer current size.");
    }

    return word;
}

int32_t NetBuffer_ReadUInt16(NetBuffer_t *netBuffer)
{
    int32_t	word;

    word = (uint16_t)NetBuffer_ReadBits(netBuffer, 16);
    if (netBuffer->ReadCurByte > netBuffer->CurSize)
    {
        Net_Error_Disconnect("NetBuffer_ReadUInt16: Attempted to read beyond end of buffer current size.");
    }

    return word;
}

int32_t NetBuffer_ReadDWord(NetBuffer_t *netBuffer)
{
    int32_t	dword;

    dword = NetBuffer_ReadBits(netBuffer, 32);
    if (netBuffer->ReadCurByte > netBuffer->CurSize)
    {
        Net_Error_Disconnect("NetBuffer_ReadDword: Attempted to read beyond end of buffer current size.");
    }

    return dword;
}

void NetBuffer_ReadData(NetBuffer_t *netBuffer, void *data, int32_t dataLength)
{
    int32_t byteIndex;
    for (byteIndex = 0; byteIndex < dataLength; byteIndex++)
    {
        ((int8_t *)data)[byteIndex] = NetBuffer_ReadByte(netBuffer);
    }
}

// net struct -> Buffer functions
//----------------------------------------------------------------------------------------------------------

void NetBuffer_WriteDeltaNetWall(NetBuffer_t *netBuffer, const netWall_t *from, const netWall_t *to)
{
    const   int32_t         cMaxStructs = MAXWALLS;
    const   int32_t         cFieldsInStruct = ARRAY_LEN(WallFields);

    int32_t         fieldIndex,
        maxChgIndex;

    netField_t      *fieldPtr;

    int32_t         *fromField,
        *toField;

    // all fields should be 32 bits to avoid any compiler packing issues
    // the "number" field is not part of the field list
    //
    // if this assert fails, check that all of the entries in net*_t are in the engine's type
    Bassert(cFieldsInStruct + 1 == (sizeof(*from) / 4));
    Bassert(to);
    Bassert(from);

    if (to->netIndex < 0 || to->netIndex >= cMaxStructs)
    {
        Net_Error_Disconnect("Netbuffer_WriteDeltaNetWall: Invalid To NetIndex");
        return;
    }

    maxChgIndex = 0;

    for (fieldIndex = 0, fieldPtr = WallFields; fieldIndex < cFieldsInStruct; fieldIndex++, fieldPtr++)
    {
        fromField = (int32_t *)((int8_t *)from + fieldPtr->offset);
        toField = (int32_t *)((int8_t *)to + fieldPtr->offset);
        if (*fromField != *toField)
        {
            maxChgIndex = fieldIndex + 1;
        }
    }

    if (maxChgIndex == 0)  // no fields changed
    {
        return;     // write nothing at all
    }

    NetBuffer_WriteBits(netBuffer, to->netIndex, NETINDEX_BITS);

    NetBuffer_WriteBits(netBuffer, maxChgIndex, STRUCTINDEX_BITS);

    for (fieldIndex = 0, fieldPtr = WallFields; fieldIndex < maxChgIndex; fieldIndex++, fieldPtr++)
    {
        fromField = (int32_t *)((int8_t *)from + fieldPtr->offset);
        toField = (int32_t *)((int8_t *)to + fieldPtr->offset);

        //                                                              // Bit(s) meaning
        //                                                              //-------------------
        if (*fromField == *toField)
        {
            NetBuffer_WriteBits(netBuffer, 0, 1);                       // field not changed
            continue;
        }

        NetBuffer_WriteBits(netBuffer, 1, 1);                           // field changed

        if (*toField == 0)
        {
            NetBuffer_WriteBits(netBuffer, 0, 1);                       // zero this field
        }
        else
        {
            NetBuffer_WriteBits(netBuffer, 1, 1);                       // don't zero this field
            NetBuffer_WriteBits(netBuffer, *toField, fieldPtr->bits);   // new field value	
        }

    }
}

void NetBuffer_WriteDeltaNetSector(NetBuffer_t *netBuffer, const netSector_t *from, const netSector_t *to)
{
    const	int32_t         cMaxStructs = MAXSECTORS;
    const	int32_t         cFieldsInStruct = ARRAY_LEN(SectorFields);

    int32_t         fieldIndex,
        maxChgIndex;

    netField_t      *fieldPtr;

    int32_t         *fromField,
        *toField;

    // all fields should be 32 bits to avoid any compiler packing issues
    // the "number" field is not part of the field list
    //
    // if this assert fails, check that all of the entries in net*_t are in the engine's type
    Bassert(cFieldsInStruct + 1 == (sizeof(*from) / 4));
    Bassert(to);
    Bassert(from);

    if (to->netIndex < 0 || to->netIndex >= cMaxStructs)
    {
        Net_Error_Disconnect("Netbuffer_WriteDeltaNetSector: Invalid To NetIndex");
        return;
    }

    maxChgIndex = 0;

    for (fieldIndex = 0, fieldPtr = SectorFields; fieldIndex < cFieldsInStruct; fieldIndex++, fieldPtr++)
    {
        fromField = (int32_t *)((int8_t *)from + fieldPtr->offset);
        toField = (int32_t *)((int8_t *)to + fieldPtr->offset);

        if (*fromField != *toField)
        {
            maxChgIndex = fieldIndex + 1;
        }
    }

    if (maxChgIndex == 0)  // no fields changed
    {
        return;     // write nothing at all
    }

    NetBuffer_WriteBits(netBuffer, to->netIndex, NETINDEX_BITS);

    NetBuffer_WriteBits(netBuffer, maxChgIndex, STRUCTINDEX_BITS);

    for (fieldIndex = 0, fieldPtr = SectorFields; fieldIndex < maxChgIndex; fieldIndex++, fieldPtr++)
    {
        fromField = (int32_t *)((int8_t *)from + fieldPtr->offset);
        toField = (int32_t *)((int8_t *)to + fieldPtr->offset);

        //                                                              // Bit(s) meaning
        //                                                              //-------------------
        if (*fromField == *toField)
        {
            NetBuffer_WriteBits(netBuffer, 0, 1);                       // field not changed
            continue;
        }

        NetBuffer_WriteBits(netBuffer, 1, 1);                           // field changed

        if (*toField == 0)
        {
            NetBuffer_WriteBits(netBuffer, 0, 1);                       // zero this field
        }
        else
        {
            NetBuffer_WriteBits(netBuffer, 1, 1);                       // don't zero this field
            NetBuffer_WriteBits(netBuffer, *toField, fieldPtr->bits);      // new field value
        }

    }
}




void NetBuffer_WriteDeltaNetActor(NetBuffer_t* netBuffer, const netactor_t *from, const netactor_t* to, int8_t writeDeletedActors)
{
    const   int32_t         cMaxStructs = MAXSPRITES;
    const   int32_t         cFieldsInStruct = ARRAY_LEN(ActorFields);

    int32_t         fieldIndex,
        maxChgIndex;

    netField_t      *fieldPtr;

    int32_t         *fromField,
        *toField;

    netactor_t defaultActor = cNullNetActor;
    Net_InitNetActor(&defaultActor);

    if ((from == NULL) && (to == NULL))
    {
        // Actor was deleted in the "from" snapshot and it's still deleted in the "to" snasphot, don't write anything.
        return;
    }

    if (from == NULL)
    {
        // The actor was deleted in the "From" snapshot, but it's there in the "To" snapshot, so that means it has been inserted in the "To" snapshot.        
        from = &defaultActor;
    }

    if (to == NULL)
    {
        // The actor was present in the "From" snapshot but it's now deleted in the "To" snapshot.
        NetBuffer_WriteBits(netBuffer, from->netIndex, NETINDEX_BITS);                      // {<NetIndex>}     sprite index
        NetBuffer_WriteBits(netBuffer, 1, 1);                                               // {<NetIndex>, 1}  sprite deleted
        return;
    }

    // all fields should be 32 bits to avoid any compiler packing issues
    // the "number" field is not part of the field list
    //
    // if this assert fails, check that all of the entries in net*_t are in the engine's type
    Bassert(cFieldsInStruct + 1 == (sizeof(*from) / 4));

    if (to->netIndex < 0 || to->netIndex >= cMaxStructs)
    {
        Net_Error_Disconnect("Netbuffer_WriteDeltaNetActor: Invalid To NetIndex");
        return;
    }

    maxChgIndex = 0;

    for (fieldIndex = 0, fieldPtr = ActorFields; fieldIndex < cFieldsInStruct; fieldIndex++, fieldPtr++)
    {
        fromField = (int32_t *)((int8_t *)from + fieldPtr->offset);
        toField = (int32_t *)((int8_t *)to + fieldPtr->offset);

        if (*fromField != *toField)
        {
            maxChgIndex = fieldIndex + 1;
        }
    }

    if (maxChgIndex == 0)
    {

        if (!writeDeletedActors)
        {
            // no fields changed, if we're not forcing a write for each actor then we're done
            return;
        }

        // write two bits for no change
        // as in, write {0,0} to indicate that the entity still exists,
        // but has not changed.
        NetBuffer_WriteBits(netBuffer, to->netIndex, NETINDEX_BITS);                    // {<NetIndex>}         sprite index
        NetBuffer_WriteBits(netBuffer, 0, 1);                                           // {<NetIndex>, 0}      sprite NOT deleted
        NetBuffer_WriteBits(netBuffer, 0, 1);                                           // {<NetIndex>, 0,0}    sprite has NOT changed.

        return;

    }

    // if we got to this point, the sprite / actor exists and has changed
    NetBuffer_WriteBits(netBuffer, to->netIndex, NETINDEX_BITS);                        // {<NetIndex>}         sprite index
    NetBuffer_WriteBits(netBuffer, 0, 1);                                               // {<NetIndex>, 0}      sprite/actor NOT deleted.
    NetBuffer_WriteBits(netBuffer, 1, 1);                                               // {<NetIndex>, 0,1}    sprite/actor HAS changed.

                                                                                        //--------------------------------------------------
                                                                                        // then...

    NetBuffer_WriteBits(netBuffer, maxChgIndex, STRUCTINDEX_BITS);                      // Write Max change index

                                                                                        // For each field in struct...

    for (fieldIndex = 0, fieldPtr = ActorFields; fieldIndex < maxChgIndex; fieldIndex++, fieldPtr++)
    {
        const int8_t isFloatingPointField = (fieldPtr->bits == 0);

        fromField = (int32_t *)((int8_t *)from + fieldPtr->offset);
        toField = (int32_t *)((int8_t *)to + fieldPtr->offset);

        if (*fromField == *toField)
        {
            NetBuffer_WriteBits(netBuffer, 0, 1);                                       // {0}              field not changed
            continue;

        }

        NetBuffer_WriteBits(netBuffer, 1, 1);                                           // {1}              field changed

        if (isFloatingPointField)
        {
            int32_t tRawFloatData = *(toField);

            NetBuffer_WriteDeltaFloat(netBuffer, tRawFloatData);                         // {1, <special float behavior>}

        }
        else
        {
            if (*toField == 0)
            {
                NetBuffer_WriteBits(netBuffer, 0, 1);                                    // {1, 0}           Zero this field

            }
            else
            {
                NetBuffer_WriteBits(netBuffer, 1, 1);                                    // {1, 1}           don't zero this int field
                NetBuffer_WriteBits(netBuffer, *toField, fieldPtr->bits);                    // {1, 1, <value>}  new field value

            }

        }

    }

}



void Net_WriteNetActorsToBuffer(NetBuffer_t* netBuffer, const netmapstate_t* from, const netmapstate_t* to)
{
    const netactor_t* fromActor = NULL;

    const netactor_t* toActor = NULL;

    int16_t     actorIndex = 0;

    int16_t     fromMaxIndex = 0;

    if (!from)
    {
        fromMaxIndex = 0;
    }
    else
    {
        fromMaxIndex = from->maxActorIndex;
    }

    while (
        actorIndex < to->maxActorIndex
        ||
        actorIndex < fromMaxIndex
        )
    {

        // load actor pointers using actor indexes
        if (actorIndex >= to->maxActorIndex)
        {
            toActor = NULL;
        }
        else
        {
            toActor = &to->actor[actorIndex];

            if (toActor->spr_sectnum == MAXSECTORS)
            {
                toActor = NULL;
            }
        }


        if (actorIndex >= fromMaxIndex)
        {
            fromActor = NULL;
        }
        else
        {
            fromActor = &from->actor[actorIndex];

            if (fromActor->spr_sectnum == MAXSECTORS)
            {
                fromActor = NULL;
            }
        }

        NetBuffer_WriteDeltaNetActor(netBuffer, fromActor, toActor, 0);

        actorIndex++;

    }
}


void Net_WriteWorldToBuffer(NetBuffer_t* netBuffer, const netmapstate_t* fromSnapshot, const netmapstate_t* toSnapshot)
{
    int32_t index = 0;

    for (index = 0; index < numwalls; index++)
    {
        Bassert(index < MAXWALLS);

        const netWall_t* fromWall = &fromSnapshot->wall[index];
        const netWall_t* toWall = &toSnapshot->wall[index];

        NetBuffer_WriteDeltaNetWall(netBuffer, fromWall, toWall);

    }

    NetBuffer_WriteBits(netBuffer, cSTOP_PARSING_CODE, NETINDEX_BITS);



    for (index = 0; index < numsectors; index++)
    {
        Bassert(index < MAXSECTORS);

        const netSector_t* fromSector = &fromSnapshot->sector[index];
        const netSector_t* toSector = &toSnapshot->sector[index];

        NetBuffer_WriteDeltaNetSector(netBuffer, fromSector, toSector);
    }

    NetBuffer_WriteBits(netBuffer, cSTOP_PARSING_CODE, NETINDEX_BITS);

    Net_WriteNetActorsToBuffer(netBuffer, fromSnapshot, toSnapshot);

    NetBuffer_WriteBits(netBuffer, cSTOP_PARSING_CODE, NETINDEX_BITS); // end of actors/sprites

}


// buffer -> net struct functions
//----------------------------------------------------------------------------------------------------------

// I agonized over whether to just make a more generic NetBuffer_ReadDeltaWorldEntry since there's so little 
// difference between walls and sectors for this function but my attempts ended up being much harder
// to read and probably more error prone than using "clipboard" inheritance, I really hope I made the right call here...

void NetBuffer_ReadDeltaWall(NetBuffer_t *netBuffer, const netWall_t *from, netWall_t *to, uint16_t netIndex)
{
    int32_t         fieldIndex,
        maxChgIndex;                               // the number of fields that changed in this delta struct from the server

    const   int32_t         cMaxStructs = MAXWALLS;
    const   int32_t         cStructFields = ARRAY_LEN(WallFields);     // the number of fields in the full struct

    netField_t      *cFieldsArray = &(WallFields[0]);

    netField_t      *field;

    int32_t         *fromField,
        *toField;

    if (netIndex < 0 || netIndex >= cMaxStructs)
    {
        Net_Error_Disconnect("NetBuffer_ReadDeltaWall: Bad Netindex to read.");
        return;
    }

    if (from->netIndex != netIndex)
    {
        Net_Error_Disconnect("Unexpected from struct netIndex. from struct netIndex should match the netIndex parameter.");
        return;
    }

    maxChgIndex = NetBuffer_ReadBits(netBuffer, STRUCTINDEX_BITS);

    if (maxChgIndex  > cStructFields || maxChgIndex < 0)
    {
        Net_Error_Disconnect("NetBuffer_ReadDeltaWall: Invalid delta field count from client.");
        return;
    }

    to->netIndex = netIndex;

    // for each field of the delta struct
    for (fieldIndex = 0, field = cFieldsArray; fieldIndex < maxChgIndex; fieldIndex++, field++)
    {
        fromField = (int32_t *)((int8_t *)from + field->offset);
        toField = (int32_t *)((int8_t *)to + field->offset);

        // no change to this field
        if (!NetBuffer_ReadBits(netBuffer, 1))
        {
            *toField = *fromField;

        }

        // field has changed
        else
        {
            // zero the field
            if (NetBuffer_ReadBits(netBuffer, 1) == 0)
            {
                *toField = 0;

            }

            // read the whole field
            else
            {
                *toField = NetBuffer_ReadBits(netBuffer, field->bits);

            }

        }
    }

    // if the delta for this struct doesn't include every field of the struct (likely)
    // just set every field after the end of this delta struct to the "from" value
    for (fieldIndex = maxChgIndex, field = &cFieldsArray[maxChgIndex]; fieldIndex < cStructFields; fieldIndex++, field++)
    {
        fromField = (int32_t *)((int8_t *)from + field->offset);
        toField = (int32_t *)((int8_t *)to + field->offset);

        // no change on all the rest of the fields of the struct
        *toField = *fromField;
    }


}

void NetBuffer_ReadDeltaSector(NetBuffer_t *netBuffer, const netSector_t *from, netSector_t *to, uint16_t netIndex)
{
    int32_t         fieldIndex,
        maxChgIndex;                                    // the number of fields that changed in this delta struct from the server

    const   int32_t         cMaxStructs = MAXSECTORS;
    const   int32_t         cStructFields = ARRAY_LEN(SectorFields);       // the number of fields in the full struct

    netField_t      *cFieldsArray = &(SectorFields[0]);

    netField_t      *field;

    int32_t         *fromField,
        *toField;


    if (netIndex < 0 || netIndex >= cMaxStructs)
    {
        Net_Error_Disconnect("NetBuffer_ReadDeltaSector: Bad Netindex to read.");
        return;
    }

    if (from->netIndex != netIndex)
    {
        Net_Error_Disconnect("Unexpected from struct netIndex. from struct netIndex should match the netIndex parameter.");
        return;
    }

    maxChgIndex = NetBuffer_ReadBits(netBuffer, STRUCTINDEX_BITS);

    if (maxChgIndex  > cStructFields || maxChgIndex < 0)
    {
        Net_Error_Disconnect("NetBuffer_ReadDeltaSector: Invalid delta field count from client.");
        return;
    }

    to->netIndex = netIndex;

    // for each field of the delta struct
    for (fieldIndex = 0, field = cFieldsArray; fieldIndex < maxChgIndex; fieldIndex++, field++)
    {
        fromField = (int32_t *)((int8_t *)from + field->offset);
        toField = (int32_t *)((int8_t *)to + field->offset);

        // no change to this field
        if (!NetBuffer_ReadBits(netBuffer, 1))
        {
            *toField = *fromField;

        }

        // field has changed
        else
        {
            // zero the field
            if (NetBuffer_ReadBits(netBuffer, 1) == 0)
            {
                *toField = 0;

            }

            // read the whole field
            else
            {
                *toField = NetBuffer_ReadBits(netBuffer, field->bits);

            }

        }
    }

    // if the delta for this struct doesn't include every field of the struct (likely)
    // just set every field after the end of this delta struct to the "from" value
    for (fieldIndex = maxChgIndex, field = &cFieldsArray[maxChgIndex]; fieldIndex < cStructFields; fieldIndex++, field++)
    {
        fromField = (int32_t *)((int8_t *)from + field->offset);
        toField = (int32_t *)((int8_t *)to + field->offset);

        // no change on all the rest of the fields of the struct
        *toField = *fromField;
    }


}



void NetBuffer_ReadDeltaActor(NetBuffer_t* netBuffer, const netactor_t *from, netactor_t *to, uint16_t actorIndex)
{
    int32_t         fieldIndex,
        maxChgIndex;                                   // the number of fields that changed in this delta struct from the server

    const	int32_t         cMaxStructs = MAXWALLS;
    const	int32_t         cStructFields = ARRAY_LEN(ActorFields);        // the number of fields in the full struct

    netField_t      *cFieldsArray = &(ActorFields[0]);

    netField_t      *field;

    int32_t         *fromField,
        *toField;

    int32_t         removeActor;

    int32_t         actorChanged; // unchanged actors aren't read

    int32_t         fromActorDeleted = (from->spr_sectnum == MAXSECTORS);

    int32_t         actorIndexOK = ((fromActorDeleted) || (from->netIndex == actorIndex));

    if (actorIndex < 0 || actorIndex >= cMaxStructs)
    {
        Net_Error_Disconnect("NetBuffer_ReadDeltaActor: Bad Netindex to read.");
    }

    if (!actorIndexOK)
    {
        Net_Error_Disconnect("Unexpected from struct netIndex. from struct netIndex should match the netIndex parameter.");
        return;
    }


    removeActor = NetBuffer_ReadBits(netBuffer, 1);                                    // read actor deleted bit

                                                                                       // if this actor is being deleted, fill it with zeros and set its netIndex to STOP_PARSING_CODE
    if (removeActor == 1)
    {
        Net_InitNetActor(to);

        return;
    }

    actorChanged = NetBuffer_ReadBits(netBuffer, 1);                                     // read actor changed bit

    to->netIndex = actorIndex;

    if (actorChanged == 0)
    {
        *to = *from;

        if (to->netIndex == cSTOP_PARSING_CODE)
        {
            Net_Error_Disconnect("Read invalid netindex from delta actor buffer.");
        }

        return;
    }

    maxChgIndex = NetBuffer_ReadBits(netBuffer, STRUCTINDEX_BITS);                          // max change index

    if (maxChgIndex  > cStructFields || maxChgIndex < 0)
    {
        Net_Error_Disconnect("NetBuffer_ReadDeltaActor: Invalid delta field count from server.");
    }

    // for each field of the delta struct
    for (fieldIndex = 0, field = cFieldsArray; fieldIndex < maxChgIndex; fieldIndex++, field++)
    {
        int8_t isFloatField = (field->bits == 0);

        fromField = (int32_t *)((int8_t *)from + field->offset);
        toField = (int32_t *)((int8_t *)to + field->offset);

        // no change to this field
        if (!NetBuffer_ReadBits(netBuffer, 1))
        {
            *toField = *fromField;

        }

        // field has changed
        else
        {
            if (isFloatField)
            {
                int32_t notZeroed = NetBuffer_ReadBits(netBuffer, 1);

                if (notZeroed == 0)
                {
                    *(float *)toField = 0.0f;

                }

                else
                {
                    int32_t sentAsRealFloat = NetBuffer_ReadBits(netBuffer, 1);

                    if (sentAsRealFloat == 0)
                    {
                        // float was written as a truncated FLOAT_INT_BITS integer
                        int32_t truncated = NetBuffer_ReadBits(netBuffer, FLOAT_INT_BITS);

                        // remove bias to allow this to be signed
                        truncated -= cTruncInt_Max;

                        *(float *)toField = (float)truncated;
                    }

                    else
                    {
                        // read the raw float data directly from the buffer
                        *toField = NetBuffer_ReadBits(netBuffer, 32);
                    }
                }
            }

            // not a float field
            else
            {

                // zero the field
                if (NetBuffer_ReadBits(netBuffer, 1) == 0)
                {
                    *toField = 0;

                }

                // read the whole field
                else
                {
                    *toField = NetBuffer_ReadBits(netBuffer, field->bits);

                }
            }

        }
    }

    // if the delta for this struct doesn't include every field of the struct (likely)
    // just set every field after the end of this delta struct to the "from" value
    for (fieldIndex = maxChgIndex, field = &cFieldsArray[maxChgIndex]; fieldIndex < cStructFields; fieldIndex++, field++)
    {
        fromField = (int32_t *)((int8_t *)from + field->offset);
        toField = (int32_t *)((int8_t *)to + field->offset);

        // no change on all the rest of the fields of the struct
        *toField = *fromField;
    }

    if (to->netIndex == cSTOP_PARSING_CODE)
    {
        Net_Error_Disconnect("Read invalid netindex from delta actor buffer.");
    }

}

// Using oldSnapshot as the "From" snapshot, parse the data in netBuffer as a diff from
// oldSnapshot to make newSnapshot.
void Net_ParseWalls(NetBuffer_t *netBuffer, netmapstate_t *oldSnapshot, netmapstate_t *newSnapshot)
{

    int32_t         newSnapshotNetIndex = 0;
    int32_t         oldSnapshotNetIndex = 0;



    netWall_t       *oldSnapshotStruct = NULL;
    netWall_t       *newSnapshotStruct = NULL;

    const   int32_t         cMaxStructIndex = numwalls - 1;

    int32_t         DEBUG_NumberOfStructsInPacket = 0;

    int32_t         DEBUG_FirstChangedIndex = -1234;
    int32_t         DEBUG_LastChangedIndex = -1234;



#if DEBUG_CHANGEDINDEXES
    int32_t                 DEBUG_ChangedIndexes[NetNumberOfIndexes];

    memset(&DEBUG_ChangedIndexes, 0, sizeof(DEBUG_ChangedIndexes));

#endif

    //read each struct from the delta packet, until the NetIndex == the stop number
    while (1)
    {
        // read the netIndex of this struct
        newSnapshotNetIndex = NetBuffer_ReadBits(netBuffer, NETINDEX_BITS);

        if (newSnapshotNetIndex < 0)
        {
            Net_Error_Disconnect("Net_ParseWalls: Invalid netIndex from client.");
            return;
        }

        //================================================================
        // DEBUG ONLY

        if (DEBUG_FirstChangedIndex < 0)
        {
            DEBUG_FirstChangedIndex = newSnapshotNetIndex;
        }

        if (newSnapshotNetIndex != cSTOP_PARSING_CODE)
        {
            DEBUG_LastChangedIndex = newSnapshotNetIndex;
        }

        //----------------------------------------------------------------

        if (newSnapshotNetIndex == (cSTOP_PARSING_CODE))
        {
            break;
        }

        if (newSnapshotNetIndex > cMaxStructIndex)
        {
            // this assert is only here to test the STOP_PARSING_CODE because we need that code to work for actors/sprites,
            // since the number of walls/sectors does not change, it's safe to return here.
            Bassert(0);
            break;
        }

        newSnapshotStruct = &(newSnapshot->wall[newSnapshotNetIndex]);
        oldSnapshotStruct = &(oldSnapshot->wall[oldSnapshotNetIndex]);

        if (netBuffer->ReadCurByte > netBuffer->CurSize)
        {
            Net_Error_Disconnect("Net_ParseWalls: Reached end of buffer without finding a stop code.");
            return;
        }

        // index up to the point where the changed structs start
        while (oldSnapshotNetIndex < newSnapshotNetIndex)
        {
            // if it's not in the delta entity set, just use the previous snapshot's value
            newSnapshot->wall[oldSnapshotNetIndex] = oldSnapshot->wall[oldSnapshotNetIndex];
            oldSnapshotNetIndex++;
            oldSnapshotStruct = &(oldSnapshot->wall[oldSnapshotNetIndex]);

        }

        // the struct referred to by oldindex is the same struct as the one referred to by newindex,
        // compare the two structs
        if (oldSnapshotNetIndex == newSnapshotNetIndex)
        {
            NetBuffer_ReadDeltaWall(netBuffer, oldSnapshotStruct, newSnapshotStruct, newSnapshotNetIndex);

            oldSnapshotNetIndex++;

            DEBUG_NumberOfStructsInPacket++;

#if DEBUG_CHANGEDINDEXES
            DEBUG_ChangedIndexes[newSnapshotNetIndex] = 1;
#endif

        }

    }

    // No more walls changed for the new snapshot, but there's still walls in the old snapshot.
    //
    // all of those old walls are unchanged. Copy all of these into the new snapshot.
    while (oldSnapshotNetIndex < numwalls)
    {
        newSnapshot->wall[oldSnapshotNetIndex] = oldSnapshot->wall[oldSnapshotNetIndex];

        oldSnapshotNetIndex++;
    }

}

void Net_ParseSectors(NetBuffer_t *netBuffer, netmapstate_t *oldSnapshot, netmapstate_t *newSnapshot)
{

    int32_t         newSnapshotNetIndex = 0;
    int32_t         oldSnapshotNetIndex = 0;



    netSector_t     *oldSnapshotStruct = NULL;
    netSector_t     *newSnapshotStruct = NULL;

    const   int32_t         cMaxStructIndex = numsectors - 1;

    int32_t         DEBUG_PrevNewIndex = -1;

    int32_t         DEBUG_FirstChangedIndex = -1234;
    int32_t         DEBUG_LastChangedIndex = -1234;



#if DEBUG_CHANGEDINDEXES
    int32_t                 DEBUG_ChangedIndexes[NetNumberOfIndexes];

    memset(&DEBUG_ChangedIndexes, 0, sizeof(DEBUG_ChangedIndexes));

#endif

    //read each struct from the delta packet, until the NetIndex == the stop number
    while (1)
    {
        // read the netIndex of this struct
        newSnapshotNetIndex = NetBuffer_ReadBits(netBuffer, NETINDEX_BITS);

        //================================================================
        // DEBUG ONLY

        if (DEBUG_FirstChangedIndex < 0)
        {
            DEBUG_FirstChangedIndex = newSnapshotNetIndex;
        }

        if (newSnapshotNetIndex != cSTOP_PARSING_CODE)
        {
            DEBUG_LastChangedIndex = newSnapshotNetIndex;
        }

        //----------------------------------------------------------------


        if (newSnapshotNetIndex < DEBUG_PrevNewIndex)
        {
            Net_Error_Disconnect("Error: Sectors were recieved out of order.");
        }

        DEBUG_PrevNewIndex = newSnapshotNetIndex;

        if (newSnapshotNetIndex < 0)
        {
            Net_Error_Disconnect("Net_ParseSectors: Invalid netIndex from client.");
            return;
        }

        if (newSnapshotNetIndex == (cSTOP_PARSING_CODE))
        {
            break;
        }

        if (newSnapshotNetIndex > cMaxStructIndex)
        {
            // this assert is only here to test the STOP_PARSING_CODE because we need that code to work for actors/sprites,
            // since the number of walls/sectors does not change, it's safe to return here.
            //
            // also this can help catch corrupted packets
            Bassert(0);
            break;
        }

        newSnapshotStruct = &(newSnapshot->sector[newSnapshotNetIndex]);
        oldSnapshotStruct = &(oldSnapshot->sector[oldSnapshotNetIndex]);

        if (netBuffer->ReadCurByte > netBuffer->CurSize)
        {
            Net_Error_Disconnect("Net_ParseSectors: Reached end of buffer without finding a stop code.");
            return;
        }

        // index up to the point where the changed structs start
        while (oldSnapshotNetIndex < newSnapshotNetIndex)
        {
            // if it's not in the delta entity set, just use the previous snapshot's value
            newSnapshot->sector[oldSnapshotNetIndex] = oldSnapshot->sector[oldSnapshotNetIndex];
            oldSnapshotNetIndex++;
            oldSnapshotStruct = &(oldSnapshot->sector[oldSnapshotNetIndex]);
        }

        // the struct referred to by oldindex is the same struct as the one referred to by newindex,
        // compare the two structs
        if (oldSnapshotNetIndex == newSnapshotNetIndex)
        {
            NetBuffer_ReadDeltaSector(netBuffer, oldSnapshotStruct, newSnapshotStruct, newSnapshotNetIndex);

            oldSnapshotNetIndex++;

#if DEBUG_CHANGEDINDEXES
            DEBUG_ChangedIndexes[newSnapshotNetIndex] = 1;
#endif

        }

    }

    // No more sectors changed for the new snapshot, but there's still sectors in the old snapshot.
    //
    // all of those old sectors are unchanged. Copy all of these into the new snapshot.
    while (oldSnapshotNetIndex < numsectors)
    {
        newSnapshot->sector[oldSnapshotNetIndex] = oldSnapshot->sector[oldSnapshotNetIndex];

        oldSnapshotNetIndex++;
    }

}

void Net_ParseActors(NetBuffer_t *netBuffer, const netmapstate_t* oldSnapshot, netmapstate_t* newSnapshot)
{

    // FROM PACKET: index into sprite[] (game arrays)
    int32_t         newActorIndex = 0;

    // INCREMENTED: index into sprite[] (game arrays)
    int32_t         oldActorIndex = 0;

    const   netactor_t      *oldSnapshotStruct = NULL;
    netactor_t      *newSnapshotStruct = NULL;

    const   int32_t         cActorIndex_OutOfOldFrameActors = 99999;

    int32_t         DEBUG_FirstChangedIndex = -1234;
    int32_t         DEBUG_LastChangedIndex = -1234;



#if DEBUG_CHANGEDINDEXES
    int32_t                 DEBUG_ChangedIndexes[NetNumberOfIndexes];

    memset(&DEBUG_ChangedIndexes, 0, sizeof(DEBUG_ChangedIndexes));

#endif

    newSnapshot->maxActorIndex = 0;

    if (!oldSnapshot)
    {
        oldActorIndex = cActorIndex_OutOfOldFrameActors;
    }
    else
    {
        oldSnapshotStruct = &oldSnapshot->actor[oldActorIndex];
    }

    //read each struct from the delta packet, until the NetIndex == the stop number
    //i.e., for each actor in the actors section of the packet...
    while (1)
    {
        newActorIndex = NetBuffer_ReadBits(netBuffer, NETINDEX_BITS);

        //================================================================
        // DEBUG ONLY

        if (DEBUG_FirstChangedIndex < 0)
        {
            DEBUG_FirstChangedIndex = newActorIndex;
        }

        if (newActorIndex != cSTOP_PARSING_CODE)
        {
            DEBUG_LastChangedIndex = newActorIndex;
        }

        //----------------------------------------------------------------

        if (newActorIndex == cSTOP_PARSING_CODE)
        {
            //NOTE: This is okay, there is a while loop below to handle cases
            //      where there are unchanged actors after the last changed actor in the
            //      netbuffer.
            break;
        }

        if (netBuffer->ReadCurByte > netBuffer->CurSize)
        {
            Net_Error_Disconnect("Net_ParseActors: Reached end of buffer without finding a stop code.");
        }

        if ((newActorIndex < 0) || (newActorIndex > MAXSPRITES))
        {
            Net_Error_Disconnect("Net_ParseActors: Invalid netIndex from client.");
        }

        // loop up to the point where the structs changed between the old frame and the new frame start
        while (oldActorIndex < newActorIndex)
        {
            //actor is unchanged in the new snapshot, just *copy* the previous snapshot's value
            newSnapshot->actor[oldActorIndex] = oldSnapshot->actor[oldActorIndex];

            oldActorIndex++;

        }

        // NOTE that actors deleted for the new snapshot will be processed *here*. New actors that "fill in gaps"
        // (rather than being added to the end) in the actor list will be processed here too.
        if (oldActorIndex == newActorIndex)
        {
            oldSnapshotStruct = &(oldSnapshot->actor[oldActorIndex]);
            newSnapshotStruct = &(newSnapshot->actor[newActorIndex]);

            NetBuffer_ReadDeltaActor(netBuffer, oldSnapshotStruct, newSnapshotStruct, newActorIndex);

            oldActorIndex++;

#if DEBUG_CHANGEDINDEXES
            DEBUG_ChangedIndexes[newSnapshotStruct->netIndex] = 1;
#endif

            continue; // because we handled the newActorIndex read from the packet.
        }

        // This actor is a newly inserted actor, either to be put in a space between
        // two actors in the old snapshot, or after the end of the old snapshot.
        //
        // Note that the "no more oldframe entities" index constant runs this
        if (oldActorIndex > newActorIndex)
        {
            newSnapshotStruct = &(newSnapshot->actor[newActorIndex]);

            netactor_t defaultActor = cNullNetActor;

            Net_InitNetActor(&defaultActor);

            NetBuffer_ReadDeltaActor(netBuffer, &defaultActor, newSnapshotStruct, newActorIndex);

        }
    }

    // No more actors changed for the new snapshot, but there's still actors in the old snapshot.
    //
    // All of those old actors are unchanged. Copy all of these into the new snapshot.
    // Remember that deleting an actor counts as a "change".
    while (oldActorIndex < MAXSPRITES)
    {
        newSnapshot->actor[oldActorIndex] = oldSnapshot->actor[oldActorIndex];

        oldActorIndex++;

    }

    NET_75_CHECK++; // For now every snapshot will have MAXSPRITES entries
    newSnapshot->maxActorIndex = MAXSPRITES;
}



void NetBuffer_ReadWorldSnapshotFromBuffer(NetBuffer_t* netBuffer, netmapstate_t* oldSnapshot, netmapstate_t* newSnapshot)
{
    // note that the order these functions are called should match the order that these structs are written to
    // by the server
    Net_ParseWalls(netBuffer, oldSnapshot, newSnapshot);
    Net_ParseSectors(netBuffer, oldSnapshot, newSnapshot);
    Net_ParseActors(netBuffer, oldSnapshot, newSnapshot);

}

static uint32_t FromRevisionToSend(uint32_t clientRevisionNumber, uint32_t serverRevisionNumber)
{
    NET_75_CHECK++; // during the rollover state it might be a good idea to init the map state history? 
                    // maybe not? I do init map states before using them, so it might not be needed.

    bool        playerRevisionIsTooOld = (serverRevisionNumber - clientRevisionNumber) > NET_REVISIONS;

    // to avoid the client thinking that revision 2 is older than revision 0xFFFF_FFFF, and ignoring it,
    // send packets to take the client from the map's initial state until the client reports back
    // that it's beyond that rollover threshold.
    bool       revisionInRolloverState = (clientRevisionNumber > serverRevisionNumber);

    // these states are never "too old"
    if (
            (clientRevisionNumber == cNoMapStateRevisionNumber)
        ||  (clientRevisionNumber == cMapStartStateRevisionNumber)
        )
    {
        return clientRevisionNumber;
    }
    if (playerRevisionIsTooOld || revisionInRolloverState)
    {
        return cMapStartStateRevisionNumber;
    }
    else
    {
        // note: includes if clientRevisionNumber == the start state or no state
        return clientRevisionNumber;
    }

}

static uint32_t ToRevisionToSend(uint32_t clientRevisionNumber, uint32_t serverRevisionNumber)
{
    if (clientRevisionNumber == cNoMapStateRevisionNumber)
    {
        return cMapStartStateRevisionNumber;
    }
    else
    {
        return serverRevisionNumber;
    }
}



void Net_SendWorldUpdate(uint32_t clientRevisionNumber, uint32_t serverRevisionNumber, int32_t sendToPlayerIndex)
{
    if (sendToPlayerIndex == myconnectindex)
    {
        return;
    }

    EDUKE32_STATIC_ASSERT(MAX_WORLDBUFFER == ARRAY_LEN(tempnetbuf));
    EDUKE32_STATIC_ASSERT(NET_REVISIONS == ARRAY_LEN(g_mapStateHistory));

    uint32_t fromRevision = FromRevisionToSend(clientRevisionNumber, serverRevisionNumber);

    uint32_t toRevision = ToRevisionToSend(clientRevisionNumber, serverRevisionNumber);

    Bassert(fromRevision != toRevision);
    Bassert(toRevision != cNoMapStateRevisionNumber);


    NetBuffer_t     netBuffer;
    NetBuffer_t*    pNetBuffer = &netBuffer;

    // note: not enough stack memory to put the world data as a local variable
    uint8_t*        packetBytes = &tempnetbuf[1];
    
    netmapstate_t*  fromMapState = NULL;

    netmapstate_t* toMapState = NULL;

    if (fromRevision == cNoMapStateRevisionNumber)
    {
        fromMapState = &NullMapState;
    }
    else if (fromRevision = cMapStartStateRevisionNumber)
    {
        fromMapState = &g_mapStartState;
    }
    else
    {
        fromMapState = &g_mapStateHistory[fromRevision % NET_REVISIONS];
    }


    if (toRevision == cMapStartStateRevisionNumber)
    {
        toMapState = &g_mapStartState;
    }
    else
    {
        toMapState = &g_mapStateHistory[toRevision % NET_REVISIONS];
    }

    Bassert(fromMapState->revisionIndex == fromRevision);
    Bassert(toMapState->revisionIndex == toRevision);


    Bmemset(packetBytes, 0, sizeof(tempnetbuf));

    tempnetbuf[0] = PACKET_WORLD_UPDATE;

    NetBuffer_Init(pNetBuffer, packetBytes, MAX_WORLDBUFFER);

    NetBuffer_WriteDword(pNetBuffer, fromMapState->revisionIndex);
    NetBuffer_WriteDword(pNetBuffer, toMapState->revisionIndex);

    Net_WriteWorldToBuffer(pNetBuffer, fromMapState, toMapState);

    if (sendToPlayerIndex > g_netServer->peerCount)
    {
        Net_Error_Disconnect("No peer for player.");
        return;
    }

    const ENetPacketFlag optimizedFlags = (ENetPacketFlag)(ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);

    NET_75_CHECK++; // HACK: I Really need to keep the peer with the player instead of assuming that the peer index is the same as the (player index - 1)
    ENetPeer *const tCurrentPeer = &g_netServer->peers[sendToPlayerIndex - 1];
    enet_peer_send(tCurrentPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf, pNetBuffer->CurSize + 1, 0));
    Dbg_PacketSent(PACKET_WORLD_UPDATE);

    DumpStatsAndSects();


}

void Net_CopySnapshotToGameArrays(netmapstate_t* snapshot)
{
    int32_t index;

    // this isn't just copying the entries that "changed" into the game arrays, 
    // remember that we need to correct any incorrect guesses the client made.
    // don't do memcpy either, e.g., sizeof(netWall_t) != sizeof(walltype)

    for (index = 0; index < numwalls; index++)
    {
        netWall_t*  snapshotWall = &(snapshot->wall[index]);
        walltype*   gameWall = &(wall[index]);

        Net_CopyWallFromNet(snapshotWall, gameWall);

    }

    for (index = 0; index < numsectors; index++)
    {
        netSector_t*  snapshotSector = &(snapshot->sector[index]);
        sectortype*   gameSector = &(sector[index]);

        Net_CopySectorFromNet(snapshotSector, gameSector);
    }
    
    Net_CopyActorsToGameArrays(snapshot);
}

// clients only
void Net_RecieveWorldUpdate(uint8_t* packetData, int32_t packetSize)
{
    if (!g_netClient)
    {
        return;
    }

    NetBuffer_t     buffer;
    NetBuffer_t*    bufferPtr = &buffer;
    uint8_t*        dataStartAddr = packetData + 1;

    int16_t         DEBUG_NoMapLoaded = ((numwalls < 1) || (numsectors < 1));
    int32_t         DEBUG_InitialSnapshotNotSet = (g_mapStartState.sector[0].wallnum <= 0);

    if (!ClientPlayerReady)
    {
        return;
    }

    NetBuffer_Init(bufferPtr, dataStartAddr, MAX_WORLDBUFFER);

    bufferPtr->CurSize = packetSize - 1;

    uint32_t packetFromRevisionNumber = NetBuffer_ReadDWord(bufferPtr);
    uint32_t packetToRevisionNumber = NetBuffer_ReadDWord(bufferPtr);

    bool from_NoMapState = (packetFromRevisionNumber == cNoMapStateRevisionNumber);

    bool from_IsInitialState = (packetFromRevisionNumber == cMapStartStateRevisionNumber);

    uint32_t clientRevisionIsTooOld = (packetToRevisionNumber - g_netMapRevisionNumber) > NET_REVISIONS;

    // Can't calculate a diff against a revision the client doesn't have.
    if (packetFromRevisionNumber > g_netMapRevisionNumber)
    {
        OSD_Printf("Received unexpected (too new) revision %d\n", packetFromRevisionNumber);
        return;
    }

    netmapstate_t* fromMapState = NULL;

    if (from_NoMapState)
    {
        // in theory the client might be able to just say "no thanks" to hard reset snapshots, but it seems like a good idea
        // for the client to always accept them.
        fromMapState = &NullMapState;
        
    }
    else
    {
        if (clientRevisionIsTooOld && !from_IsInitialState)
        {
            // this is actually not a major problem, an alternative way to handle this would be to just ignore the packet and hope the
            // server sends a diff from the initial revision eventually.
            //
            // NOTE: If you get this bug on connect it really is a problem, the client never seems to get updated to the initial revision when
            // this happens. It seems like if you don't wat enough time before connecting again after a crash you will get this to happen.
            //
            // actually I've seen that happen even when this error didn't happen.
            Net_Error_Disconnect("Internal Error: Net_ReadWorldUpdate(): Client From map state too old, but server did not send snapshot from initial state.");
            return;
        }

        if (from_IsInitialState)
        {
            // clients must always accept the initial map state as a snapshot,
            // If a client's current revision is too old, the server will send them 
            // a snapshot to update them from the initial state instead of the 
            // client's last known state.
            fromMapState = &g_mapStartState;
        }
        else if (packetToRevisionNumber <= g_netMapRevisionNumber)
        {
            // clients should just ignore revisions that are older than their current revision
            // (packets can arrive out of order). Note that the server will send cInitialMapStateRevisionNumber
            // as the "From" index when the revision counter rolls over.
            return;
        }
        else
        {
            fromMapState = &g_mapStateHistory[packetFromRevisionNumber % NET_REVISIONS];
        }
    }


    netmapstate_t* toMapState = NULL;

    if (packetToRevisionNumber == cNoMapStateRevisionNumber)
    {
        // revision 0 should never be modified
        Bassert(packetToRevisionNumber != cNoMapStateRevisionNumber);
        OSD_Printf("Received map state for revision 0, but revision 0 should never be updated. Ignoring.\n");
        return;
    }
    

    if (packetToRevisionNumber == cMapStartStateRevisionNumber)
    {
        // If (to = the map start state), anything but (from = cNoMapStateRevisionNumber) should never happen
        Bassert(packetFromRevisionNumber == cNoMapStateRevisionNumber);

        if (packetFromRevisionNumber != cNoMapStateRevisionNumber)
        {
            OSD_Printf("Received map state for initial state, but 'from' was not cNoMapStateRevisionNumber. Ignoring.\n");
            return;
        }
            

        // we should really only be getting this if we are going from revision 0 (null)

        NET_75_CHECK++; // come up with a way to make it so that clients still know that the map changed while still ignoring duplicate map start states

        // allow the server to send over many initial map states, it's not great because it means
        // the client startup is a big laggy, but the client doesn't really have any other way to know
        // if the map changed.

        toMapState = &g_mapStartState;
    }
    else
    {
        toMapState = &g_mapStateHistory[packetToRevisionNumber % NET_REVISIONS];
    }

    uint32_t DEBUG_OldClientRevision = g_netMapRevisionNumber;

    Bassert(fromMapState);
    NetBuffer_ReadWorldSnapshotFromBuffer(bufferPtr, fromMapState, toMapState);

    toMapState->revisionIndex = packetToRevisionNumber;

    g_netMapRevisionNumber = packetToRevisionNumber;

    if (packetToRevisionNumber == cMapStartStateRevisionNumber)
    {
        OSD_Printf("Received initial snapshot. Resetting level.");
        Net_ClearAllLinkedLists();
    }

    
    // this makes it impossible for the client to have linked list errors but it causes a severe frame rate drop
    //Net_ClearAllLinkedLists();
   

    Net_CopySnapshotToGameArrays(toMapState);

    if (packetToRevisionNumber == cMapStartStateRevisionNumber)
    {
        resetpspritevars(MODE_GAME);
    }

}



void Net_SendMapUpdate(void)
{
    if (g_netClient || !g_netServer || numplayers < 2)
    {
        return;
    }

    // Don't add these revisions to the map state history, and don't send them, they're stored elsewhere.
    // Also the server needs to get past these revisions itself before it can start thinking of sending data to clients.
    if (
            (g_netMapRevisionNumber == cNoMapStateRevisionNumber)
        ||  (g_netMapRevisionNumber == cMapStartStateRevisionNumber)
        )
    {
        return;
    }

    if (g_netMapRevisionNumber == UINT32_MAX)
    {
        // we should be able to recover from this
        OSD_Printf("Net_SendMapUpdates(): [Note] Map state revision number overflow.");
        g_netMapRevisionNumber = cStartingRevisionIndex;
    }
    else
    {
        g_netMapRevisionNumber++;
    }

    netmapstate_t* srvCurrentMapState = &g_mapStateHistory[g_netMapRevisionNumber % NET_REVISIONS];

    Net_InitMapState(srvCurrentMapState);
    Net_AddWorldToSnapshot(srvCurrentMapState);

    srvCurrentMapState->revisionIndex = g_netMapRevisionNumber;

    int32_t playerIndex = 0;

    for (TRAVERSE_CONNECT(playerIndex))
    {
        if (playerIndex == myconnectindex)
        {
            // there's no point in the server sending itself a snapshot.
            continue;
        }

        uint32_t playerRevisionNumber = g_player[playerIndex].revision;

        Net_SendWorldUpdate(playerRevisionNumber, g_netMapRevisionNumber, playerIndex);
    }

}



#if 0

//Goes by picnum, can only be set in spriteflags (defs.con)
int32_t Net_PicnumIsDefinedClientSide(int16_t picnum)
{
    return (g_tile[picnum].flags & SFLAG_CLIENTSIDEONLY);
}

//Can possibly be changed by a modder but it's really not recommended
int32_t Net_ActorIsClientSide(int32_t spriteIndex)
{
    return (actor[spriteIndex].flags & SFLAG_CLIENTSIDEONLY);
}

#endif





//-------------------------------------------------------------------------------------------------

#endif  // !defined NETCODE_DISABLE
