//------------------------------------------------------------------------- /* 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. */ //------------------------------------------------------------------------- /// Preprocessor directives: /// /// ITERATE_ON_UPDATE: Every time the netcode updates the sprite linked lists, attempt to iterate through all of them. /// SPRLIST_PRINT: Print in the console every time a sprite's status in the link list changes due to /// PACKET_RECV_PRINT: Print all received packets /// NETCODE_DISABLE: Remove pretty much all of the game's interaction with the code in this file. #include "duke3d.h" #include "game.h" #include "gamedef.h" #include "network.h" #include "premap.h" #include "savegame.h" #include "input.h" #include "enet/enet.h" #include "lz4.h" #include "crc32.h" #include "vfs.h" // Data needed even if netcode is disabled 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_networkMode = NET_CLIENT; // to support (gcc only) -f-strict-aliasing, the netcode needs to specify that its 32 bit chunks to and from the // packet code should not be subject to strict aliasing optimizations // it would appear that only GCC does this, Clang doesn't seem to and Microsoft Visual C++ definitely doesn't. #ifdef __GNUC__ #define TYPE_PUNNED __attribute__((__may_alias__)) #else #define TYPE_PUNNED #endif typedef TYPE_PUNNED int32_t NetChunk32; // Unfortunately faketimerhandler needs extra "help" because the Build Engine source doesn't include network.h. #ifdef NETCODE_DISABLE void faketimerhandler(void) { ; } #else void faketimerhandler(void) { if (g_netServer == NULL && g_netClient == NULL) return; enet_host_service(g_netServer ? g_netServer : g_netClient, NULL, 0); } static void Net_Disconnect(void); static void Net_HandleClientPackets(void); static void Net_HandleServerPackets(void); #endif void Net_GetPackets(void) { timerUpdate(); MUSIC_Update(); S_Update(); G_HandleSpecialKeys(); #ifndef NETCODE_DISABLE 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(); } #endif } // (the rest of the file) #ifndef NETCODE_DISABLE // this attribute is for variables that are only used to highlight interesting conditions in the code // when I'm in a debugger. #define NET_DEBUG_VAR EDUKE32_UNUSED // internal data #define SNAPSHOTS_TO_SAVE 32 #define MAX_SNAPSHOT_ACTORS 256 #define MAX_SNAPSHOT_WALLS MAXWALLS // 16384 #define MAX_SNAPSHOT_SECTORS MAXSECTORS // 4096 //////////////////////////////////////////////////////////////////////////////// // 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) #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) // Client player ready for world updates (player spawned and ready) static int ClientPlayerReady = 0; // all zero NetActor static const netactor_t cNullNetActor = netactor_t(); // all zero NetWall static const netWall_t cNullNetWall = netWall_t(); // all zero NetSector static const netSector_t cNullNetSector = netSector_t(); // the revision number reserved for the state of the map when it was loaded from the .map file. // NOTE: Don't confuse this with a revision index. Index 0 into the snapshot history is NOT the initial map state. // g_MapStateHistory[0] could be revision 0, revision 64, revision 128, etc... static const uint32_t cInitialMapStateRevisionNumber = 0; // the revision array starts here instead of zero (may as well have the initial map state be revision 0)... static const uint32_t cStartingRevisionIndex = 1; static const int32_t cLocSprite_DeletedSpriteStat = MAXSTATUS; #ifdef CURRENTLY_UNUSED static const int32_t cNetSprite_DeletedSpriteStat = STAT_NETALLOC; #endif //[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]; static char recbuf[180]; static int32_t g_chatPlayer = -1; // 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_SIZE(WallFields) * 2) + (MAXSECTORS * ARRAY_SIZE(SectorFields) * 2) + (MAXSPRITES * ARRAY_SIZE(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 typedef struct netField_s { const char *name; // field name int32_t offset; // offset from the start of the entity struct int32_t bits; // field size } netField_t; #define SECTF(x) #x,(int32_t)(size_t)&((netSector_t*)0)->x static netField_t SectorFields[] = { { SECTF(wallptr), 16 }, { SECTF(wallnum), 16 }, { SECTF(ceilingz), 32 }, { SECTF(floorz), 32 }, { SECTF(ceilingstat), 16 }, { SECTF(floorstat), 16 }, { SECTF(ceilingpicnum), 16 }, { SECTF(ceilingheinum), 16 }, { SECTF(ceilingshade), 8 }, { SECTF(ceilingpal), 8 }, { SECTF(ceilingxpanning), 8 }, { SECTF(ceilingypanning), 8 }, { SECTF(floorpicnum), 16 }, { SECTF(floorheinum), 16 }, { SECTF(floorshade), 8 }, { SECTF(floorpal), 8 }, { SECTF(floorxpanning), 8 }, { SECTF(floorypanning), 8 }, { SECTF(visibility), 8 }, { SECTF(fogpal), 8 }, { SECTF(lotag), 16 }, { SECTF(hitag), 16 }, { SECTF(extra), 16 }, }; #undef SECTF #define WALLF(x) #x,(int32_t)(size_t)&((netWall_t*)0)->x static netField_t WallFields[] = { { WALLF(x), 32 }, { WALLF(y), 32 }, { WALLF(point2), 16 }, { WALLF(nextwall), 16 }, { WALLF(nextsector), 16 }, { WALLF(cstat), 16 }, { WALLF(picnum), 16 }, { WALLF(overpicnum), 16 }, { WALLF(shade), 8 }, { WALLF(pal), 8 }, { WALLF(xrepeat), 8 }, { WALLF(yrepeat), 8 }, { WALLF(xpanning), 8 }, { WALLF(ypanning), 8 }, { WALLF(lotag), 16 }, { WALLF(hitag), 16 }, { WALLF(extra), 16 }, }; #undef WALLF #define ACTF(x) #x,(int32_t)(size_t)&((netactor_t*)0)->x static netField_t ActorFields[] = { { ACTF(t_data_0), 32 }, { ACTF(t_data_1), 32 }, { ACTF(t_data_2), 32 }, { ACTF(t_data_3), 32 }, { ACTF(t_data_4), 32 }, { ACTF(t_data_5), 32 }, { ACTF(t_data_6), 32 }, { ACTF(t_data_7), 32 }, { ACTF(t_data_8), 32 }, { ACTF(t_data_9), 32 }, #ifdef LUNATIC // need to update this section if LUNATIC is ever brought back in { ACTF(hvel), 16 }, { ACTF(vvel), 16 }, { ACTF(startframe), 16 }, { ACTF(numframes), 16 }, { ACTF(viewtype), 16 }, { ACTF(incval), 16 }, { ACTF(delay), 16 }, #endif { ACTF(flags), 32 }, { ACTF(bpos_x), 32 }, { ACTF(bpos_y), 32 }, { ACTF(bpos_z), 32 }, { ACTF(floorz), 32 }, { ACTF(ceilingz), 32 }, { ACTF(lastvx), 32 }, { ACTF(lastvy), 32 }, { ACTF(lasttransport), 8}, { ACTF(picnum), 16 }, { ACTF(ang), 16 }, { ACTF(extra), 16 }, { ACTF(owner), 16 }, { ACTF(movflag), 16 }, { ACTF(tempang), 16 }, { ACTF(timetosleep), 16 }, { ACTF(stayput), 16 }, { ACTF(dispicnum), 16 }, #if defined LUNATIC { ACTF(movflags), 16 }, #endif { ACTF(cgg), 8}, //------------------------------------------------------ // sprite fields { ACTF(spr_x), 32 }, { ACTF(spr_y), 32 }, { ACTF(spr_z), 32 }, { ACTF(spr_cstat), 16 }, { ACTF(spr_picnum), 16 }, { ACTF(spr_shade), 8 }, { ACTF(spr_pal), 8 }, { ACTF(spr_clipdist), 8 }, { ACTF(spr_blend), 8 }, { ACTF(spr_xrepeat), 8 }, { ACTF(spr_yrepeat), 8 }, { ACTF(spr_xoffset), 8 }, { ACTF(spr_yoffset), 8 }, { ACTF(spr_sectnum), 16 }, { ACTF(spr_statnum), 16 }, { ACTF(spr_ang), 16 }, { ACTF(spr_owner), 16 }, { ACTF(spr_xvel), 16 }, { ACTF(spr_yvel), 16 }, { ACTF(spr_zvel), 16 }, { ACTF(spr_lotag), 16 }, { ACTF(spr_hitag), 16 }, { ACTF(spr_extra), 16 }, //-------------------------------------------------------------- //spriteext fields { ACTF(ext_mdanimtims), 32 }, { ACTF(ext_mdanimcur), 16 }, { ACTF(ext_angoff), 16 }, { ACTF(ext_pitch), 16 }, { ACTF(ext_roll), 16 }, { ACTF(ext_offset_x), 32 }, { ACTF(ext_offset_y), 32 }, { ACTF(ext_offset_z), 32 }, { ACTF(ext_flags), 8 }, { ACTF(ext_xpanning), 8 }, { ACTF(ext_ypanning), 8 }, { ACTF(ext_alpha), 0 }, // float //-------------------------------------------------------------- //spritesmooth fields { ACTF(sm_smoothduration), 0 }, // float { ACTF(sm_mdcurframe), 16 }, { ACTF(sm_mdoldframe), 16 }, { ACTF(sm_mdsmooth), 16 }, }; #undef ACTF // 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 #ifdef CURRENTLY_UNUSED // Just so you can get an idea of how much memory the netcode needs... 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; #endif // 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 static uint32_t g_netMapRevisionNumber = 0; // In addition to the client keeping track of what revision the server is using // (g_netRevisionNumber), it also increments its own revision that represents // what version of the game the client has interpolated to. static uint32_t g_cl_InterpolatedRevision = 0; static netmapstate_t g_mapStartState; static netmapstate_t g_cl_InterpolatedMapStateHistory[NET_REVISIONS]; // 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 % NET_REVISONS static netmapstate_t g_mapStateHistory[NET_REVISIONS]; static uint8_t tempnetbuf[MAX_WORLDBUFFER]; // Remember that this constant needs to be one bit longer than a struct index, 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); static uint32_t NET_75_CHECK; // Externally available data / functions int32_t g_netPlayersWaiting = 0; int32_t g_netIndex = 2; newgame_t pendingnewgame; bool g_enableClientInterpolationCheck = true; // Internal functions static void Net_ReadWorldUpdate(uint8_t *packetData, int32_t packetSize); //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. static 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; #ifdef SPRLIST_PRINT OSD_Printf("DEBUG: Inserted scratch pad sprite %d\n", spriteIndex); #endif } static void Net_DeleteFromSect(int16_t spriteIndex) { if(spriteIndex >= MAXSPRITES) { return; } if(sprite[spriteIndex].sectnum >= MAXSECTORS) { return; } do_deletespritesect(spriteIndex); } static void Net_DeleteFromStat(int16_t spriteIndex) { if(spriteIndex >= MAXSPRITES) { return; } if(sprite[spriteIndex].statnum >= MAXSTATUS) { return; } do_deletespritestat(spriteIndex); } static 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); Net_DeleteFromSect(spritenum); sprite[spritenum].sectnum = MAXSECTORS; } static void Net_InitScratchPadSpriteList() { if (!g_netClient) { return; } headscratchpadsprite = -1; Bmemset(&nextscratchpadsprite[0], -1, sizeof(nextscratchpadsprite)); } static void Net_DeleteAllScratchPadSprites() { if (!g_netClient) { return; } int16_t spriteIndex = headscratchpadsprite; for (spriteIndex = headscratchpadsprite; spriteIndex >= 0; spriteIndex = nextscratchpadsprite[spriteIndex]) { Net_DoDeleteSprite(spriteIndex); #ifdef SPRLIST_PRINT OSD_Printf("DEBUG: Deleted scratch pad sprite (set to STAT_NETALLOC) %d\n", spriteIndex); #endif } Net_InitScratchPadSpriteList(); } static 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); } static void Net_InitNetActor(netactor_t *netActor) { *(netActor) = cNullNetActor; netActor->netIndex = cSTOP_PARSING_CODE; } // Low level "Copy net structs to / from game structs" functions //------------------------------------------------------------------------------ // Net -> Game Arrays //------------------------------------------------------------------------------ static 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? } static void Net_CopySectorFromNet(netSector_t* netSector, sectortype* gameSector) { Bassert(gameSector); Bassert(netSector); // (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."); } } // Try to catch infinite loops in the sprite linked lists, unfortunately this rarely works. EDUKE32_UNUSED static void Test_Iterate() { int32_t watchdogIndex = 0; const int32_t cThreshold = MAXSPRITES * 2; 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); } } } } static void Net_UpdateSpriteLinkedLists(int16_t spriteIndex, const netactor_t* snapActor) { if ((spriteIndex >= MAXSPRITES) || (spriteIndex < 0)) { Net_Error_Disconnect("Can't update linked lists. Actor index invalid."); } int16_t oldGameStatnum = sprite[spriteIndex].statnum; // 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 , 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 // // 6. game sprite Normal, snap sprite Normal -- change change // [1] if (snapActor->spr_statnum == oldGameStatnum) { return; } bool snapActorIsNormalStat = (snapActor->spr_statnum != MAXSTATUS) && (snapActor->spr_statnum != STAT_NETALLOC); bool gameSpriteIsNormalStat = (oldGameStatnum != MAXSTATUS) && (oldGameStatnum != STAT_NETALLOC); // [2] if ( snapActor->spr_statnum == MAXSTATUS) { #ifdef SPRLIST_PRINT OSD_Printf("DEBUG: Sprite %d: Case 2 (delete game sprite)\n", spriteIndex); #endif Net_DeleteFromStat(spriteIndex); Net_DeleteFromSect(spriteIndex); return; } // [3] else if (oldGameStatnum == MAXSTATUS) { #ifdef SPRLIST_PRINT OSD_Printf("DEBUG: Sprite %d: Case 3 (insert new game sprite)\n", spriteIndex); #endif do_insertsprite_at_headofstat(spriteIndex, snapActor->spr_statnum); do_insertsprite_at_headofsect(spriteIndex, snapActor->spr_sectnum); } // [4] else if ((oldGameStatnum == STAT_NETALLOC) && snapActorIsNormalStat) { #ifdef SPRLIST_PRINT OSD_Printf("DEBUG: Sprite %d: Case 4 (STAT_NETALLOC to stat %d)\n", spriteIndex, snapActor->spr_statnum); #endif changespritestat(spriteIndex, snapActor->spr_statnum); do_insertsprite_at_headofsect(spriteIndex, snapActor->spr_sectnum); } // [5] else if (gameSpriteIsNormalStat && (snapActor->spr_statnum == STAT_NETALLOC)) { #ifdef SPRLIST_PRINT OSD_Printf("DEBUG: Sprite %d: Case 5 (normal to STAT_NETALLOC)\n", spriteIndex); #endif changespritestat(spriteIndex, snapActor->spr_statnum); Net_DeleteFromSect(spriteIndex); } // [6] else if(gameSpriteIsNormalStat && snapActorIsNormalStat) { #ifdef SPRLIST_PRINT OSD_Printf("DEBUG: Sprite %d: Case 6 (normal stat to normal stat)\n", spriteIndex); #endif // note that these functions handle cases where the game sprite already has that stat/sectnum changespritestat(spriteIndex, snapActor->spr_statnum); changespritesect(spriteIndex, snapActor->spr_sectnum); } #ifdef SPRLIST_PRINT 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]); #endif NET_DEBUG_VAR bool invalid = ( (nextspritesect[spriteIndex] == spriteIndex) && (nextspritestat[spriteIndex] == spriteIndex) ); Bassert(!invalid); #ifdef ITERATE_ON_UPDATE Test_Iterate(); #endif } 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; // don't set statnum / sectnum here, that should be done in Net_UpdateSpriteLinkedLists, // otherwise it's harder than it has to be because most of the engine functions // asssume that if the sprite's stat / sectnum matches there's nothing to do as // far as the linked lists are concerned 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); // This seemed to make enemy movements smoother. bool aiIDChanged = (gameActor->t_data[5] != netActor->t_data_5); // If the sprite is a CON sprite, don't overwrite AC_Action_Count bool isActor = G_HaveActor(netActor->spr_picnum); // Fixes ambient sound infinite sound replay glitch (stand in the outdoor area of E1L1, the "airplane noise" will get very loud and loop endlessly. bool isSoundActor = (DYNAMICTILEMAP(netActor->picnum) == MUSICANDSFX); if(!isSoundActor) { gameActor->t_data[0] = netActor->t_data_0; gameActor->t_data[1] = netActor->t_data_1; gameActor->t_data[4] = netActor->t_data_4; gameActor->t_data[5] = netActor->t_data_5; } // Prevents: // - Rotating sector stuttering // - Trains running backwards bool isSyncedSE = (DYNAMICTILEMAP(netActor->picnum) == SECTOREFFECTOR) && ( (netActor->spr_lotag == SE_0_ROTATING_SECTOR) || (netActor->spr_lotag == SE_1_PIVOT) || (netActor->spr_lotag == SE_6_SUBWAY) || (netActor->spr_lotag == SE_11_SWINGING_DOOR) || (netActor->spr_lotag == SE_14_SUBWAY_CAR) || (netActor->spr_lotag == SE_30_TWO_WAY_TRAIN) ); if (aiIDChanged || isSyncedSE) { gameActor->t_data[2] = netActor->t_data_2; } if (aiIDChanged || !isActor) { gameActor->t_data[3] = netActor->t_data_3; } 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->stayput = netActor->stayput; 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; } static 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 static 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); } static void Net_CopyActorsToGameArrays(const netmapstate_t* srv_snapshot, const netmapstate_t* cl_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 ((srv_snapshot->maxActorIndex) < 0 || (srv_snapshot->maxActorIndex > MAXSPRITES)) { Net_Error_Disconnect("Net_CopyActorsToGameArrays: Invalid number of actors in snapshot."); } for (actorIndex = 0; actorIndex < MAXSPRITES; actorIndex++) { const netactor_t* srvActor = &(srv_snapshot->actor[actorIndex]); const netactor_t* clActor = &(cl_snapshot->actor[actorIndex]); int status = memcmp(srvActor, clActor, sizeof(netactor_t)); if(status == 0) { if(g_enableClientInterpolationCheck) { continue; } } const netactor_t* snapshotActor = srvActor; spritetype* gameSprite = &(sprite[actorIndex]); actor_t* gameActor = &(actor[actorIndex]); spriteext_t* gameExt = &(spriteext[actorIndex]); spritesmooth_t* gameSm = &(spritesmooth[actorIndex]); NET_DEBUG_VAR int32_t DEBUG_GameSprOldStat = gameSprite->statnum; NET_DEBUG_VAR int32_t DEBUG_NetSprOldStat = snapshotActor->spr_statnum; // 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_CopyAllActorDataFromNet(snapshotActor, gameSprite, gameActor, gameExt, gameSm); Net_UpdateSpriteLinkedLists(actorIndex, snapshotActor); if (!snapSpriteIsDeleted) { actorCount++; } } if (actorCount > MAXSPRITES) { Net_Error_Disconnect("Net_CopyActorsToGameArrays: Too many actors in snapshot."); } Numsprites = actorCount; } //------------------------------------------------------------------------------------- // Game -> Net //------------------------------------------------------------------------------------- static 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; } static 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->stayput = gameActor->stayput; 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; } static 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 = §or[index]; netSector_t* snapshotSector = &snapshot->sector[index]; Net_CopySectorToNet(gameSector, snapshotSector, index); } Net_AddActorsToSnapshot(snapshot); } //------------------------------------------------------------------------------------------------------------------------ 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; pftq = QUOTE_RESERVED2; g_player[myconnectindex].ps->fta = 180; } } static 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); } static 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); } // sync a connecting player up with the current game state static 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].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) } 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); } static 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 static 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; } } static 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); } static 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->q16ang = update->q16ang; g_player[playerindex].ps->q16horiz = update->q16horiz; g_player[playerindex].ps->q16horizoff = update->q16horizoff; } 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; } } // Server only static 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); } static 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); } static 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); } static 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; } static void Net_CheckForEnoughVotes() { // Only the server can decide map changes if (!g_netServer || numplayers <= 1) { return; } int32_t requiredvotes; // 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++; } } int32_t numfor = 0; int32_t numagainst = 0; for (int32_t playerIndex = 0; playerIndex < MAXPLAYERS; playerIndex++) { if (g_player[playerIndex].gotvote) { if (g_player[playerIndex].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); } } static 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(); } static void Net_ReceiveMapVoteCancel(uint8_t *pbuf) { // 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; } static void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver) { uint32_t byteIndex, j; int32_t other = pbuf[packbufleng]; for (byteIndex = 1; pbuf[byteIndex]; byteIndex++) { g_player[other].user_name[byteIndex - 1] = pbuf[byteIndex]; } g_player[other].user_name[byteIndex - 1] = 0; byteIndex++; g_player[other].ps->aim_mode = pbuf[byteIndex++]; g_player[other].ps->auto_aim = pbuf[byteIndex++]; g_player[other].ps->weaponswitch = pbuf[byteIndex++]; g_player[other].ps->palookup = g_player[other].pcolor = pbuf[byteIndex++]; g_player[other].pteam = pbuf[byteIndex++]; for (j = byteIndex; byteIndex - j < 10; byteIndex++) { g_player[other].wchoice[byteIndex - j] = pbuf[byteIndex]; } if (fromserver) { g_player[other].playerquitflag = 1; } } static void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng) { NET_75_CHECK++; // possible buffer bug here with a malicious 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) { buildvfs_kfd i; if ((i = kopen4loadfrommod(boardfilename, 0)) == buildvfs_kfd_invalid) { 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; } static 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; } } static 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; } static 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; } } static void Net_ParseClientPacket(ENetEvent *event) { uint8_t *pbuf = event->packet->data; int32_t packbufleng = event->packet->dataLength; int32_t other = pbuf[--packbufleng]; NET_DEBUG_VAR enum DukePacket_t packetType = (enum DukePacket_t)pbuf[0]; #ifdef PACKET_RECV_PRINT 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; } } static 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: 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 = cInitialMapStateRevisionNumber; 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; } } } static void Net_ReceiveServerUpdate(ENetEvent *event) { serverupdate_t serverupdate; serverplayerupdate_t playerupdate; if (((event->packet->dataLength - sizeof(serverupdate_t)) % sizeof(serverplayerupdate_t)) != 0) { return; } uint8_t *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 (int playerIndex = 0; playerIndex < serverupdate.numplayers; ++playerIndex) { Bmemcpy(&playerupdate, updatebuf, sizeof(serverplayerupdate_t)); updatebuf += sizeof(serverplayerupdate_t); Net_ExtractPlayerUpdate(&playerupdate.player, PACKET_MASTER_TO_SLAVE); g_player[playerIndex].ps->gotweapon = playerupdate.gotweapon; g_player[playerIndex].ps->kickback_pic = playerupdate.kickback_pic; Bmemcpy(g_player[playerIndex].frags, playerupdate.frags, sizeof(playerupdate.frags)); Bmemcpy(g_player[playerIndex].ps->inv_amount, playerupdate.inv_amount, sizeof(playerupdate.inv_amount)); Bmemcpy(g_player[playerIndex].ps->ammo_amount, playerupdate.ammo_amount, sizeof(playerupdate.ammo_amount)); g_player[playerIndex].ps->curr_weapon = playerupdate.curr_weapon; g_player[playerIndex].ps->last_weapon = playerupdate.last_weapon; g_player[playerIndex].ps->wantweaponfire = playerupdate.wantweaponfire; g_player[playerIndex].ps->weapon_pos = playerupdate.weapon_pos; g_player[playerIndex].ps->frag_ps = playerupdate.frag_ps; g_player[playerIndex].ps->frag = playerupdate.frag; g_player[playerIndex].ps->fraggedself = playerupdate.fraggedself; g_player[playerIndex].ps->last_extra = playerupdate.last_extra; g_player[playerIndex].ping = playerupdate.ping; g_player[playerIndex].ps->newowner = playerupdate.newowner; } } // sends the version and a simple crc32 of the current password, all verified by the server before the connection can continue static 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); } static 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(); } // client only static 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) { NET_75_CHECK++; // are these extra calls to create initial snapshots necessary or is it sufficient to just have it in // G_EnterLevel? /* Net_InitMapStateHistory(); Net_AddWorldToInitialSnapshot(); */ 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; } static 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]].input) { g_player[pbuf[4]].input = (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(); } } static 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(); } static void Net_ParseServerPacket(ENetEvent *event) { uint8_t *pbuf = event->packet->data; int32_t packbufleng = event->packet->dataLength; // input_t *nsyn; NET_DEBUG_VAR enum DukePacket_t packetType = (enum DukePacket_t) pbuf[0]; --packbufleng; #ifdef PACKET_RECV_PRINT 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; } 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_ReadWorldUpdate(pbuf, packbufleng); break; default: Net_ParsePacketCommon(pbuf, packbufleng, 1); break; } } static 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); } } //////////////////////////////////////////////////////////////////////////////// // Map Update Packets //Insert a sprite from STAT_NETALLOC static 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); return i; } //////////////////////////////////////////////////////////////////////////////// // Player Updates static 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->q16ang = g_player[player].ps->q16ang; update->q16horiz = g_player[player].ps->q16horiz; update->q16horizoff = g_player[player].ps->q16horizoff; update->ping = g_player[player].ping; update->deadflag = g_player[player].ps->dead_flag; update->playerquitflag = g_player[player].playerquitflag; } //////////////////////////////////////////////////////////////////////////////// // New Game Packets // set all actors, walls, and sectors in a snapshot to their Null states. static void Net_InitMapState(netmapstate_t* mapState) { netactor_t sprDefault = cNullNetActor; int32_t index = 0; sprDefault.netIndex = cSTOP_PARSING_CODE; 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. for (index = 0; index < MAXSPRITES; index++) { mapState->actor[index] = sprDefault; } for (index = 0; index < MAXWALLS; index++) { mapState->wall[index] = cNullNetWall; } for (index = 0; index < MAXSECTORS; index++) { mapState->sector[index] = cNullNetSector; } // set the revision number to a valid but easy to identify number, // this is just to make it easier to debug. mapState->revisionNumber = 8675309; } // 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 = cInitialMapStateRevisionNumber; } } static void Net_ResetPlayerReady() { int32_t playerindex; for (TRAVERSE_CONNECT(playerindex)) { g_player[playerindex].ready = 0; } } // Packet code //--------------------------------------------------------------------------------------------------------------------------------- // 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 "network.cpp: 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 byteArray with zeros static 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; } /// ///guarantees that the destination string will be nul terminated /// static void 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; } // 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 EDUKE32_UNUSED static float Dbg_RawIntToFloat(NetChunk32 floatRawData) { float* floatPtr = (float*)&floatRawData; return *(floatPtr); } static 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; } EDUKE32_UNUSED static 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); } EDUKE32_UNUSED static 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); } EDUKE32_UNUSED static 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); } EDUKE32_UNUSED static 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); } static 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 static void NetBuffer_WriteData(NetBuffer_t *netBuffer, const void *data, int16_t length) { int32_t byteIndex; for (byteIndex = 0; byteIndex < length; byteIndex++) { NetBuffer_WriteUnsignedByte(netBuffer, ((const int8_t *)data)[byteIndex]); } } EDUKE32_UNUSED static 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); } } static void NetBuffer_WriteDeltaFloat(NetBuffer_t *netBuffer, NetChunk32 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 static 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 static 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; } EDUKE32_UNUSED static 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; } EDUKE32_UNUSED static 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; } EDUKE32_UNUSED static 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; } static 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; } EDUKE32_UNUSED static 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 //---------------------------------------------------------------------------------------------------------- static void NetBuffer_WriteDeltaNetWall(NetBuffer_t *netBuffer, const netWall_t *from, const netWall_t *to) { const int32_t cMaxStructs = MAXWALLS; const int32_t cFieldsInStruct = ARRAY_SIZE(WallFields); int32_t fieldIndex, maxChgIndex; netField_t *fieldPtr; const 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 >= cMaxStructs) { Net_Error_Disconnect("Netbuffer_WriteDeltaNetWall: Invalid To NetIndex"); return; } maxChgIndex = 0; for (fieldIndex = 0, fieldPtr = WallFields; fieldIndex < cFieldsInStruct; fieldIndex++, fieldPtr++) { fromField = (int32_t const *)((int8_t const *)from + fieldPtr->offset); toField = (int32_t const *)((int8_t const *)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 const *)((int8_t const *)from + fieldPtr->offset); toField = (int32_t const *)((int8_t const *)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 } } } static void NetBuffer_WriteDeltaNetSector(NetBuffer_t *netBuffer, const netSector_t *from, const netSector_t *to) { const int32_t cMaxStructs = MAXSECTORS; const int32_t cFieldsInStruct = ARRAY_SIZE(SectorFields); int32_t fieldIndex, maxChgIndex; netField_t *fieldPtr; const 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 >= cMaxStructs) { Net_Error_Disconnect("Netbuffer_WriteDeltaNetSector: Invalid To NetIndex"); return; } maxChgIndex = 0; for (fieldIndex = 0, fieldPtr = SectorFields; fieldIndex < cFieldsInStruct; fieldIndex++, fieldPtr++) { fromField = (int32_t const *)((int8_t const *)from + fieldPtr->offset); toField = (int32_t const *)((int8_t const *)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 const *)((int8_t const *)from + fieldPtr->offset); toField = (int32_t const *)((int8_t const *)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 } } } static 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_SIZE(ActorFields); int32_t fieldIndex, maxChgIndex; netField_t *fieldPtr; const int32_t *fromField, *toField; 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 = &cNullNetActor; } 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); // {} sprite index NetBuffer_WriteBits(netBuffer, 1, 1); // {, 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 const *)((int8_t const *)from + fieldPtr->offset); toField = (int32_t const *)((int8_t const *)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); // {} sprite index NetBuffer_WriteBits(netBuffer, 0, 1); // {, 0} sprite NOT deleted NetBuffer_WriteBits(netBuffer, 0, 1); // {, 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); // {} sprite index NetBuffer_WriteBits(netBuffer, 0, 1); // {, 0} sprite/actor NOT deleted. NetBuffer_WriteBits(netBuffer, 1, 1); // {, 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 const *)((int8_t const *)from + fieldPtr->offset); toField = (int32_t const *)((int8_t const *)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 rawFloatData = *(toField); NetBuffer_WriteDeltaFloat(netBuffer, rawFloatData); // {1, } } 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, } new field value } } } } static 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->netIndex == cSTOP_PARSING_CODE) { toActor = NULL; } } if (actorIndex >= fromMaxIndex) { fromActor = NULL; } else { fromActor = &from->actor[actorIndex]; if (fromActor->netIndex == cSTOP_PARSING_CODE) { fromActor = NULL; } } NetBuffer_WriteDeltaNetActor(netBuffer, fromActor, toActor, 0); actorIndex++; } } static 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... static 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_SIZE(WallFields); // the number of fields in the full struct netField_t *cFieldsArray = &(WallFields[0]); netField_t *field; const NetChunk32 *fromField; NetChunk32 *toField; if (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 const *)((int8_t const *)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 = (NetChunk32 const *)((int8_t const *)from + field->offset); toField = (NetChunk32 *) ((int8_t *) to + field->offset); // no change on all the rest of the fields of the struct *toField = *fromField; } } static 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_SIZE(SectorFields); // the number of fields in the full struct netField_t *cFieldsArray = &(SectorFields[0]); netField_t *field; const NetChunk32 *fromField; NetChunk32 *toField; if (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 const *)((int8_t const *)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++) { // I am not sure if this is type punning or not, just to be safe fromField = (NetChunk32 const *)((int8_t const *)from + field->offset); toField = (NetChunk32 *) ((int8_t *) to + field->offset); // no change on all the rest of the fields of the struct *toField = *fromField; } } static 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_SIZE(ActorFields); // the number of fields in the full struct netField_t *cFieldsArray = &(ActorFields[0]); netField_t *field; const NetChunk32 *fromField; NetChunk32 *toField; int32_t removeActor; int32_t actorChanged; // this is only used if the packet is sent with "force" int32_t fromActorDeleted = (from->spr_sectnum == MAXSECTORS); int32_t actorIndexOK = ((fromActorDeleted) || (from->netIndex == actorIndex)); if (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 const *)((int8_t const *)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 NetChunk32 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++) { // I am really not 100% sure if this is type punning or not, just to be safe. fromField = (NetChunk32 const *)((int8_t const *)from + field->offset); toField = (NetChunk32 *) ((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. static 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; NET_DEBUG_VAR int32_t DEBUG_NumberOfStructsInPacket = 0; NET_DEBUG_VAR int32_t DEBUG_FirstChangedIndex = -1234; NET_DEBUG_VAR int32_t DEBUG_LastChangedIndex = -1234; #if DEBUG_CHANGEDINDEXES NET_DEBUG_VAR 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++; } } static 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; NET_DEBUG_VAR int32_t DEBUG_PrevNewIndex = -1; NET_DEBUG_VAR int32_t DEBUG_FirstChangedIndex = -1234; NET_DEBUG_VAR int32_t DEBUG_LastChangedIndex = -1234; #if DEBUG_CHANGEDINDEXES NET_DEBUG_VAR 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++; } } static 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; NET_DEBUG_VAR int32_t DEBUG_FirstChangedIndex = -1234; NET_DEBUG_VAR int32_t DEBUG_LastChangedIndex = -1234; #if DEBUG_CHANGEDINDEXES NET_DEBUG_VAR 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]); NetBuffer_ReadDeltaActor(netBuffer, &cNullNetActor, 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; } static 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 void Net_SendWorldUpdate(uint32_t fromRevisionNumber, uint32_t toRevisionNumber, int32_t sendToPlayerIndex) { if (sendToPlayerIndex == myconnectindex) { return; } Bassert(MAX_WORLDBUFFER == ARRAY_SIZE(tempnetbuf)); Bassert(NET_REVISIONS == ARRAY_SIZE(g_mapStateHistory)); uint32_t playerRevisionIsTooOld = (toRevisionNumber - fromRevisionNumber) > NET_REVISIONS; // to avoid the client thinking that revision 2 is older than revision 0xFFFF_FFFF, // send packets to take the client from the map's initial state until the client reports back // that it's beyond that rollover threshold. uint32_t revisionInRolloverState = (fromRevisionNumber > toRevisionNumber); NetBuffer_t buffer; NetBuffer_t* bufferPtr = &buffer; // note: not enough stack memory to put the world data as a local variable uint8_t* byteBuffer = &tempnetbuf[1]; uint32_t fromRevisionNumberToSend = 0x86753090; netmapstate_t* toMapState = &g_mapStateHistory[toRevisionNumber % NET_REVISIONS]; netmapstate_t* fromMapState = NULL; 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. if (playerRevisionIsTooOld || revisionInRolloverState) { fromMapState = &g_mapStartState; fromRevisionNumberToSend = cInitialMapStateRevisionNumber; } else { uint32_t tFromRevisionIndex = fromRevisionNumber % NET_REVISIONS; fromMapState = &g_mapStateHistory[tFromRevisionIndex]; fromRevisionNumberToSend = fromRevisionNumber; } Bmemset(byteBuffer, 0, sizeof(tempnetbuf) - 1); tempnetbuf[0] = PACKET_WORLD_UPDATE; NetBuffer_Init(bufferPtr, byteBuffer, MAX_WORLDBUFFER); NetBuffer_WriteDword(bufferPtr, fromRevisionNumberToSend); NetBuffer_WriteDword(bufferPtr, toRevisionNumber); Net_WriteWorldToBuffer(bufferPtr, fromMapState, toMapState); if (sendToPlayerIndex > ((int32_t) g_netServer->peerCount)) { Net_Error_Disconnect("No peer for player."); return; } // in the future we could probably use these flags for enet_peer_send, for the world updates EDUKE32_UNUSED 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, bufferPtr->CurSize + 1, 0)); Dbg_PacketSent(PACKET_WORLD_UPDATE); } static void Net_CopySnapshotToGameArrays(netmapstate_t* srv_snapshot, netmapstate_t* cl_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* srvWall = &(srv_snapshot->wall[index]); netWall_t* clWall = &(cl_snapshot->wall[index]); int status = memcmp(srvWall, clWall, sizeof(netWall_t)); if(status == 0) { if(g_enableClientInterpolationCheck) { // only copy in the server's (old) value if the client's interpolation up to that point was incorrect continue; } } walltype* gameWall = &(wall[index]); Net_CopyWallFromNet(srvWall, gameWall); } for (index = 0; index < numsectors; index++) { netSector_t* srvSector = &(srv_snapshot->sector[index]); netSector_t* clSector = &(cl_snapshot->sector[index]); int status = memcmp(srvSector, clSector, sizeof(netSector_t)); if(status == 0) { if(g_enableClientInterpolationCheck) { continue; } } sectortype* gameSector = &(sector[index]); Net_CopySectorFromNet(srvSector, gameSector); } Net_CopyActorsToGameArrays(srv_snapshot, cl_snapshot); } // clients only static void Net_ReadWorldUpdate(uint8_t* packetData, int32_t packetSize) { if (!g_netClient) { return; } NetBuffer_t buffer; NetBuffer_t* bufferPtr = &buffer; uint8_t* dataStartAddr = packetData + 1; NET_DEBUG_VAR int16_t DEBUG_NoMapLoaded = ((numwalls < 1) || (numsectors < 1)); NET_DEBUG_VAR 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); uint32_t from_IsInitialState = (packetFromRevisionNumber == cInitialMapStateRevisionNumber); uint32_t clientRevisionIsTooOld = (packetToRevisionNumber - g_netMapRevisionNumber) > NET_REVISIONS; netmapstate_t* fromMapState = NULL; 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 = &g_mapStateHistory[packetToRevisionNumber % NET_REVISIONS]; netmapstate_t* clMapState = &g_cl_InterpolatedMapStateHistory[packetToRevisionNumber % NET_REVISIONS]; toMapState->revisionNumber = packetToRevisionNumber; NET_DEBUG_VAR uint32_t DEBUG_OldClientRevision = g_netMapRevisionNumber; Bassert(fromMapState); NetBuffer_ReadWorldSnapshotFromBuffer(bufferPtr, fromMapState, toMapState); g_netMapRevisionNumber = packetToRevisionNumber; Net_CopySnapshotToGameArrays(toMapState, clMapState); } // handles revision rollover static uint32_t Net_GetNextRevisionNumber(uint32_t currentNumber) { if(currentNumber == UINT32_MAX) { // we should be able to recover from this OSD_Printf("Net_SendMapUpdates(): [Note] Map state revision number overflow."); return cStartingRevisionIndex; } return (currentNumber + 1); } // --------------------------------------------------------------------------------------------------------------- // Externally accessible functions // --------------------------------------------------------------------------------------------------------------- void Net_WaitForInitialSnapshot() { while (g_netMapRevisionNumber < cStartingRevisionIndex) { G_HandleAsync(); } } // 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; } // 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_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_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_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(); } 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; } } // store the client's history so that we can go back and look and see if the client interpolated properly // (the server snapshots are always older than the client's current game tic) void Net_StoreClientState(void) { if(!g_netClient) { return; } // don't store client revisions if we don't even have a server revision if(g_netMapRevisionNumber < cStartingRevisionIndex) { return; } g_cl_InterpolatedRevision = Net_GetNextRevisionNumber(g_cl_InterpolatedRevision); netmapstate_t* currentMapState = &g_cl_InterpolatedMapStateHistory[g_cl_InterpolatedRevision % NET_REVISIONS]; Net_InitMapState(currentMapState); Net_AddWorldToSnapshot(currentMapState); currentMapState->revisionNumber = g_cl_InterpolatedRevision; } void Net_SendMapUpdate(void) { if (g_netClient || !g_netServer || numplayers < 2) { return; } g_netMapRevisionNumber = Net_GetNextRevisionNumber(g_netMapRevisionNumber); netmapstate_t* toMapState = &g_mapStateHistory[g_netMapRevisionNumber % NET_REVISIONS]; Net_InitMapState(toMapState); Net_AddWorldToSnapshot(toMapState); toMapState->revisionNumber = 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); } } void DumpMapStateHistory() { const 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; } 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); } 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); videoNextPage(); } // 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 } 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); Xfree(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 Xfree(oursrvaddr); Net_Disconnect(); } ///< summary> /// Record the current state of the game arrays to the initial snapshot /// void Net_AddWorldToInitialSnapshot() { Net_AddWorldToSnapshot(&g_mapStartState); } 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_SendUserMapName(void) { if (numplayers < 2) return; packbuf[0] = PACKET_USER_MAP; Bcorrectfilename(boardfilename, 0); // user map name is sent with a NUL at the end int32_t 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)); } } // 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) { if ((!g_netServer) && (!g_netClient)) { return insertsprite(sect, 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 and to // the null picnum so no CON gets executed if ((!g_netServer) && (!g_netClient)) { deletesprite(spritenum); return; } if (g_netClient) { sprite[spritenum].cstat = 32768; sprite[spritenum].picnum = 0; return; } Net_DoDeleteSprite(spritenum); } // 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_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); } 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_InitMapStateHistory() { int32_t mapStateIndex = 0; for (mapStateIndex = 0; mapStateIndex < NET_REVISIONS; mapStateIndex++) { netmapstate_t *mapState = &g_mapStateHistory[mapStateIndex]; netmapstate_t *clState = &g_cl_InterpolatedMapStateHistory[mapStateIndex]; Net_InitMapState(mapState); Net_InitMapState(clState); } Net_InitMapState(&g_mapStartState); g_mapStartState.revisionNumber = cInitialMapStateRevisionNumber; g_netMapRevisionNumber = cInitialMapStateRevisionNumber; // Net_InitMapStateHistory() g_cl_InterpolatedRevision = cInitialMapStateRevisionNumber; } 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; } // 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 } #endif //-------------------------------------------------------------------------------------------------