diff --git a/source/build/include/build.h b/source/build/include/build.h index 85115ff1f..87f0e451d 100644 --- a/source/build/include/build.h +++ b/source/build/include/build.h @@ -1240,6 +1240,7 @@ int32_t rayintersect(int32_t x1, int32_t y1, int32_t z1, int32_t vx, int32_t vy, #if !defined NETCODE_DISABLE void do_insertsprite_at_headofstat(int16_t spritenum, int16_t statnum); int32_t insertspritestat(int16_t statnum); +void do_deletespritestat(int16_t deleteme); void do_insertsprite_at_headofsect(int16_t spritenum, int16_t sectnum); void do_deletespritesect(int16_t deleteme); #endif diff --git a/source/build/src/engine.cpp b/source/build/src/engine.cpp index a676ced70..12cf6870a 100644 --- a/source/build/src/engine.cpp +++ b/source/build/src/engine.cpp @@ -7262,7 +7262,7 @@ LISTFN_STATIC int32_t insertspritestat(int16_t statnum) } // remove sprite 'deleteme' from its status list -static void do_deletespritestat(int16_t deleteme) +LISTFN_STATIC void do_deletespritestat(int16_t deleteme) { int32_t const sectnum = sprite[deleteme].statnum; int32_t const prev = prevspritestat[deleteme]; diff --git a/source/duke3d/src/actors.cpp b/source/duke3d/src/actors.cpp index 4550a776d..4a97933c5 100644 --- a/source/duke3d/src/actors.cpp +++ b/source/duke3d/src/actors.cpp @@ -422,6 +422,9 @@ int32_t A_MoveSpriteClipdist(int32_t spriteNum, vec3_t const * const change, uin int const isEnemy = A_CheckEnemySprite(pSprite); vec2_t const oldPos = *(vec2_t *)pSprite; + // check to make sure the netcode didn't leave a deleted sprite in the sprite lists. + Bassert(pSprite->sectnum < MAXSECTORS); + if (pSprite->statnum == STAT_MISC || (isEnemy && pSprite->xrepeat < 4)) { pSprite->x += change->x; @@ -472,6 +475,9 @@ int32_t A_MoveSpriteClipdist(int32_t spriteNum, vec3_t const * const change, uin clipmove((vec3_t *)pSprite, &newSectnum, change->x << 13, change->y << 13, clipDist, ZOFFSET6, ZOFFSET6, clipType); pSprite->z = oldZ; + // Testing: For some reason the assert below this was tripping for clients + EDUKE32_UNUSED int16_t dbg_ClipMoveSectnum = newSectnum; + if (isEnemy) { // Handle potential stayput condition (map-provided or hard-coded). @@ -501,6 +507,8 @@ int32_t A_MoveSpriteClipdist(int32_t spriteNum, vec3_t const * const change, uin pSprite->ang += 768; } + EDUKE32_UNUSED int16_t dbg_newSectnum2 = newSectnum; + if (newSectnum == -1) { newSectnum = pSprite->sectnum; @@ -619,19 +627,16 @@ void A_DeleteSprite(int spriteNum) if (sprite[spriteNum].picnum == MUSICANDSFX && actor[spriteNum].t_data[0] == 1) S_StopEnvSound(sprite[spriteNum].lotag, spriteNum); - // NetAlloc - if (Net_IsRelevantSprite(spriteNum)) - { - Net_DeleteSprite(spriteNum); - return; - } - +#ifdef NETCODE_DISABLE deletesprite(spriteNum); +#else + Net_DeleteSprite(spriteNum); +#endif } void A_AddToDeleteQueue(int spriteNum) { - if (g_deleteQueueSize == 0) + if (g_netClient || (g_deleteQueueSize == 0)) // [75] Clients should not use SpriteDeletionQueue[] and just set the sprites invisible immediately in A_DeleteSprite { A_DeleteSprite(spriteNum); return; @@ -1266,11 +1271,11 @@ ACTOR_STATIC void G_MovePlayers(void) { if (otherPlayerDist < 1400 && pPlayer->knee_incs == 0) { - // Don't stomp teammates. - if ( - ((g_gametypeFlags[ud.coop] & GAMETYPE_TDM) && pPlayer->team != g_player[otherp].ps->team) || - (!(g_gametypeFlags[ud.coop] & GAMETYPE_PLAYERSFRIENDLY) && !(g_gametypeFlags[ud.coop] & GAMETYPE_TDM)) - ) + // Don't stomp teammates. + if ( + ((g_gametypeFlags[ud.coop] & GAMETYPE_TDM) && pPlayer->team != g_player[otherp].ps->team) || + (!(g_gametypeFlags[ud.coop] & GAMETYPE_PLAYERSFRIENDLY) && !(g_gametypeFlags[ud.coop] & GAMETYPE_TDM)) + ) { pPlayer->knee_incs = 1; pPlayer->weapon_pos = -1; diff --git a/source/duke3d/src/actors.h b/source/duke3d/src/actors.h index f78851a4c..f257a331d 100644 --- a/source/duke3d/src/actors.h +++ b/source/duke3d/src/actors.h @@ -184,30 +184,167 @@ typedef struct #endif } actor_t; -// this struct needs to match the beginning of actor_t above -typedef struct +// note: fields in this struct DO NOT have to be in this order, +// however if you add something to this struct, please make sure +// a field gets added to ActorFields[], otherwise the field +// won't get synced over the network! +// +// I don't think the ActorFields array needs to be in the same order, +// need to verify this... +typedef struct netactor_s { - int32_t t_data[10]; // 40b sometimes used to hold offsets to con code + // actor fields + //-------------------------------------------- + int32_t + t_data_0, + t_data_1, + t_data_2, + t_data_3, + t_data_4, + t_data_5, + t_data_6, + t_data_7, + t_data_8, + t_data_9; #ifdef LUNATIC - struct move mv; - struct action ac; - uint16_t actiontics; - uint16_t movflags; + + int32_t + hvel, + vvel; + + + int32_t + startframe, + numframes; + + int32_t + viewtype, + incval, + delay; + + int32_t + actiontics; #endif - int32_t flags; // 4b - vec3_t bpos; // 12b - int32_t floorz, ceilingz; // 8b - vec2_t lastv; // 8b - int16_t picnum, ang, extra, owner; // 8b - int16_t movflag, tempang, timetosleep; // 6b - int16_t stayput; + int32_t + flags; - uint8_t cgg, lasttransport; - spritetype sprite; - int16_t netIndex; + int32_t + bpos_x, + bpos_y, + bpos_z; + + int32_t + floorz, + ceilingz, + lastvx, + lastvy, + + lasttransport, + + picnum, + ang, + extra, + owner, + + movflag, + tempang, + timetosleep, + + stayput, + dispicnum; + + +#if defined LUNATIC + int32_t movflags; +#endif + + // note: lightId, lightcount, lightmaxrange are not synchronized between client and server + + int32_t + cgg; + + + // sprite fields + //----------------------------- + + int32_t + spr_x, + spr_y, + spr_z, + + spr_cstat, + + spr_picnum, + + spr_shade, + + spr_pal, + spr_clipdist, + spr_blend, + + spr_xrepeat, + spr_yrepeat, + + spr_xoffset, + spr_yoffset, + + spr_sectnum, + spr_statnum, + + spr_ang, + spr_owner, + spr_xvel, + spr_yvel, + spr_zvel, + + spr_lotag, + spr_hitag, + + spr_extra; + + //--------------------------------------------- + //spriteext fields + + int32_t + ext_mdanimtims, + + ext_mdanimcur, + ext_angoff, + ext_pitch, + ext_roll, + + ext_offset_x, + ext_offset_y, + ext_offset_z, + + ext_flags, + ext_xpanning, + ext_ypanning; + + float ext_alpha; + + // DON'T send tsprites over the internet + + //-------------------------------------------- + //spritesmooth fields + + float sm_smoothduration; + + int32_t + sm_mdcurframe, + sm_mdoldframe, + + sm_mdsmooth; + + //-------------------------------------------- + // SpriteProjectile fields + + // may want to put projectile fields here + int32_t netIndex; + } netactor_t; #pragma pack(pop) diff --git a/source/duke3d/src/cmdline.cpp b/source/duke3d/src/cmdline.cpp index 2486df4e6..ffbeed6ac 100644 --- a/source/duke3d/src/cmdline.cpp +++ b/source/duke3d/src/cmdline.cpp @@ -62,6 +62,7 @@ void G_ShowParameterHelp(void) "-mh [file.def]\tInclude an additional definitions module\n" "-mx [file.con]\tInclude an additional CON script module\n" "-m\t\tDisable enemies\n" + "-noffire\t\tDisable friendly fire\n" #ifndef EDUKE32_STANDALONE "-nam\t\tRun in NAM compatibility mode\n" "-napalm\t\tRun in NAPALM compatibility mode\n" @@ -176,6 +177,7 @@ void G_CheckCommandLine(int32_t argc, char const * const * argv) ud.m_respawn_inventory = 0; ud.warp_on = 0; ud.cashman = 0; + ud.m_ffire = 1; ud.m_player_skill = ud.player_skill = 2; g_player[0].wchoice[0] = 3; g_player[0].wchoice[1] = 4; @@ -420,6 +422,12 @@ void G_CheckCommandLine(int32_t argc, char const * const * argv) i++; continue; } + if (!Bstrcasecmp(c+1, "noffire")) + { + ud.m_ffire = 0; + i++; + continue; + } if (!Bstrcasecmp(c+1, "rts")) { if (argc > i+1) diff --git a/source/duke3d/src/config.cpp b/source/duke3d/src/config.cpp index 286bb811f..c492c84d3 100644 --- a/source/duke3d/src/config.cpp +++ b/source/duke3d/src/config.cpp @@ -268,7 +268,6 @@ void CONFIG_SetDefaults(void) ud.idplayers = 1; ud.levelstats = 0; ud.lockout = 0; - ud.m_ffire = 1; ud.m_marker = 1; ud.maxautosaves = 5; ud.menu_scrollbartilenum = -1; diff --git a/source/duke3d/src/game.cpp b/source/duke3d/src/game.cpp index a76af16e0..9ed0d6e3f 100644 --- a/source/duke3d/src/game.cpp +++ b/source/duke3d/src/game.cpp @@ -1268,7 +1268,16 @@ static int32_t G_InitActor(int32_t i, int32_t tilenum, int32_t set_movflag_uncon int32_t A_InsertSprite(int16_t whatsect,int32_t s_x,int32_t s_y,int32_t s_z,int16_t s_pn,int8_t s_s, uint8_t s_xr,uint8_t s_yr,int16_t s_a,int16_t s_ve,int16_t s_zv,int16_t s_ow,int16_t s_ss) { - int const newSprite = Net_IsRelevantStat(s_ss) ? Net_InsertSprite(whatsect, s_ss) : insertsprite(whatsect, s_ss); + + + int32_t newSprite; + +#ifdef NETCODE_DISABLE + newSprite = insertsprite(whatsect, s_ss); +#else + newSprite = Net_InsertSprite(whatsect, s_ss); + +#endif if (EDUKE32_PREDICT_FALSE((unsigned)newSprite >= MAXSPRITES)) { @@ -4727,9 +4736,9 @@ void G_HandleLocalKeys(void) tempbuf[ridiculeNum++] = myconnectindex; if (g_netClient) - enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(tempbuf, ridiculeNum, 0)); + enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(&tempbuf[0], ridiculeNum, 0)); else if (g_netServer) - enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(tempbuf, ridiculeNum, 0)); + enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(&tempbuf[0], ridiculeNum, 0)); #endif pus = NUMPAGES; pub = NUMPAGES; @@ -4748,9 +4757,9 @@ void G_HandleLocalKeys(void) tempbuf[2] = myconnectindex; if (g_netClient) - enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(tempbuf, 3, 0)); + enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(&tempbuf[0], 3, 0)); else if (g_netServer) - enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(tempbuf, 3, 0)); + enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(&tempbuf[0], 3, 0)); } #endif pus = NUMPAGES; @@ -6599,6 +6608,14 @@ MAIN_LOOP_RESTART: Menu_Change(MENU_MAIN); + if(g_netClient) + { + OSD_Printf("Waiting for initial snapshot..."); + Net_WaitForInitialSnapshot(); + + + } + if (g_networkMode != NET_DEDICATED_SERVER) { G_GetCrosshairColor(); @@ -6662,6 +6679,11 @@ MAIN_LOOP_RESTART: ud.warp_on = 0; KB_KeyDown[sc_Pause] = 0; // JBF: I hate the pause key + if(g_netClient) + { + ready2send = 1; // TESTING + } + do //main loop { if (handleevents() && quitevent) @@ -6981,9 +7003,16 @@ int G_DoMoveThings(void) G_AnimateWalls(); A_MoveCyclers(); - if (g_netServer && (everyothertime % 10) == 0) + if ((everyothertime % 10) == 0) { - Net_SendMapUpdate(); + if(g_netServer) + { + Net_SendMapUpdate(); + } + else if(g_netClient) + { + Net_StoreClientState(); + } } } diff --git a/source/duke3d/src/menus.cpp b/source/duke3d/src/menus.cpp index ff7bc60c9..acb82d5c1 100644 --- a/source/duke3d/src/menus.cpp +++ b/source/duke3d/src/menus.cpp @@ -1319,7 +1319,7 @@ static MenuOption_t MEO_NETOPTIONS_MAPEXITS = MAKE_MENUOPTION( &MF_Bluefont, &ME static MenuEntry_t ME_NETOPTIONS_MAPEXITS = MAKE_MENUENTRY( "Map Exits", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_MAPEXITS, Option ); static MenuOption_t MEO_NETOPTIONS_FRFIRE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OffOn, &ud.m_ffire ); static MenuEntry_t ME_NETOPTIONS_FRFIRE = MAKE_MENUENTRY( "Fr. Fire", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_FRFIRE, Option ); -static MenuEntry_t ME_NETOPTIONS_ACCEPT = MAKE_MENUENTRY( "Accept", &MF_Redfont, &MEF_NetSetup_Confirm, &MEO_NULL, Link ); +static MenuEntry_t ME_NETOPTIONS_ACCEPT = MAKE_MENUENTRY( "Accept", &MF_Redfont, &MEF_NetSetup_Confirm, &MEO_NETWORK_HOSTGAME, Link ); static MenuEntry_t *MEL_NETOPTIONS[] = { &ME_NETOPTIONS_GAMETYPE, diff --git a/source/duke3d/src/net.cpp b/source/duke3d/src/net.cpp index 1083be016..5b78390ab 100644 --- a/source/duke3d/src/net.cpp +++ b/source/duke3d/src/net.cpp @@ -1,6 +1,6 @@ //------------------------------------------------------------------------- /* -Copyright (C) 2010 EDuke32 developers and contributors +Copyright (C) 2017 EDuke32 developers and contributors This file is part of EDuke32. @@ -20,6 +20,13 @@ 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" @@ -32,58 +39,1430 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "lz4.h" #include "crc32.h" -ENetHost *g_netServer = NULL; -ENetHost *g_netClient = NULL; -ENetPeer *g_netClientPeer = NULL; -enet_uint16 g_netPort = 23513; -int32_t g_netDisconnect = 0; -char g_netPassword[32]; -int32_t g_netPlayersWaiting = 0; -#ifndef NETCODE_DISABLE -int32_t g_networkMode = NET_CLIENT; -#endif -int32_t g_netIndex = 2; -newgame_t pendingnewgame; +// 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; -#ifdef NETCODE_DISABLE -void faketimerhandler(void) {} +// 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 -static char recbuf[180]; +typedef TYPE_PUNNED int32_t NetChunk32; + + +// Unfortunately faketimerhandler needs extra "help" because the Build Engine source doesn't include net.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); +} +#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; +static const int32_t cNetSprite_DeletedSpriteStat = STAT_NETALLOC; + +//[75] When a client attempts to allocate a sprite during the game loop, which is not defined as a clientside sprite, +// the insert will go through, and the sprite will be put on this list. +// +// every time the client gets a new snapshot, this entire list will be deleted + +static int32_t headscratchpadsprite = -1; +static int32_t nextscratchpadsprite[MAXSPRITES]; + + +static char recbuf[180]; static int32_t g_chatPlayer = -1; -// sprites of these statnums are synced to clients by the server -int16_t g_netStatnums[] = +// 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 { - STAT_PROJECTILE, - //STAT_PLAYER, - STAT_STANDABLE, - //STAT_ACTIVATOR, - //STAT_TRANSPORT, - //STAT_EFFECTOR, - STAT_ACTOR, - STAT_ZOMBIEACTOR, - STAT_MISC, - STAT_DEFAULT, - STAT_NETALLOC, - MAXSTATUS + 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,(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 }, + }; -#pragma pack(push,1) -uint32_t g_netMapRevision = 0; -netmapstate_t g_mapStartState; -netmapstate_t *g_mapStateHistory[NET_REVISIONS]; -uint8_t tempnetbuf[sizeof(netmapstate_t) + 400]; -netmapdiff_t tempMapDiff; -#pragma pack(pop) -#define tempnetbufsize sizeof(tempnetbuf) +#undef SECTF + + +#define WALLF(x) #x,(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,(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 + +// 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; + + +// 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; + if (p <= 0) return; g_player[p].playerquitflag = 0; @@ -120,10 +1499,34 @@ static void P_RemovePlayer(int32_t p) } } -// sync a connecting player up with the current game state -void Net_SyncPlayer(ENetEvent *event) +static void Net_SendNewPlayer(int32_t newplayerindex) { - int32_t i, j; + 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) { @@ -137,177 +1540,60 @@ void Net_SyncPlayer(ENetEvent *event) S_PlaySound(DUKE_GETWEAPON2); // open a new slot if necessary and save off the resulting slot # for future reference - for (TRAVERSE_CONNECT(i)) + for (TRAVERSE_CONNECT(newPlayerIndex)) { - if (g_player[i].playerquitflag == 0) + if (g_player[newPlayerIndex].playerquitflag == 0) { break; } } - if (i == -1) + if (newPlayerIndex == -1) { - i = g_mostConcurrentPlayers++; + newPlayerIndex = g_mostConcurrentPlayers++; } - event->peer->data = (void *)(intptr_t)i; + 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[i].netsynctime = totalclock; - g_player[i].playerquitflag = 1; - //g_player[i].revision = g_netMapRevision; + g_player[newPlayerIndex].netsynctime = totalclock; + g_player[newPlayerIndex].playerquitflag = 1; - for (j=0; jpeer); + Net_SendNewPlayer(newPlayerIndex); + Net_SendPlayerIndex(newPlayerIndex, event->peer); Net_SendClientInfo(); Net_SendUserMapName(); - Net_SendNewGame(0, event->peer); + Net_SendNewGame(0, event->peer); // newly connecting player (Net_SyncPlayer) } -void Net_SpawnPlayer(int32_t player) -{ - int32_t j = 0; - packbuf[j++] = PACKET_PLAYER_SPAWN; - packbuf[j++] = player; - Bmemcpy(&packbuf[j], &g_player[player].ps->pos.x, sizeof(vec3_t) * 2); - j += sizeof(vec3_t) * 2; - - packbuf[j++] = 0; - - enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(packbuf, j, ENET_PACKET_FLAG_RELIABLE)); -} static void display_betascreen(void) { - rotatesprite_fs(160<<16,100<<16,65536,0,BETASCREEN,0,0,2+8+64+BGSTRETCH); + 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); + 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); + rotatesprite_fs(160 << 16, (151) << 16, 30 << 11, 0, PLUTOPAKSPRITE + 1, 0, 0, 2 + 8); } -void faketimerhandler(void) -{ - if (g_netServer==NULL && g_netClient==NULL) - return; - enet_host_service(g_netServer ? g_netServer : g_netClient, NULL, 0); -} - -void Net_WaitForServer(void) -{ - int32_t server_ready = g_player[0].pingcnt; - - if (numplayers < 2 || g_netServer) return; - - P_SetGamePalette(g_player[myconnectindex].ps, TITLEPAL, 8+2+1); - - do - { - if (quitevent || keystatus[sc_Escape]) G_GameExit(""); - - if (G_FPSLimit()) - { - display_betascreen(); - gametext_center_shade(170, "Waiting for server", 14); - } - - // XXX: this looks like something that should be rate limited... - packbuf[0] = PACKET_PLAYER_PING; - packbuf[1] = myconnectindex; - - if (g_netClientPeer) - enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(packbuf, 2, ENET_PACKET_FLAG_RELIABLE)); - - G_HandleAsync(); - - if (g_player[0].pingcnt > server_ready) - { - P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 8+2+1); - return; - } - } - while (1); -} - -void Net_ResetPrediction(void) -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// Connect/Disconnect - -void Net_Connect(const char *srvaddr) -{ - ENetAddress address; - ENetEvent event; - char *addrstr = NULL; - int32_t i; - - 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 (i=4; i>0; i--) - { - /* Wait up to 5 seconds for the connection attempt to succeed. */ - if (enet_host_service(g_netClient, & event, 5000) > 0 && - event.type == ENET_EVENT_TYPE_CONNECT) - { - initprintf("Connection to %s:%d succeeded.\n", oursrvaddr, address.port); - Bfree(oursrvaddr); - return; - } - else - { - /* Either the 5 seconds are up or a disconnect event was */ - /* received. Reset the peer in the event the 5 seconds */ - /* had run out without any significant event. */ - enet_peer_reset(g_netClientPeer); - initprintf("Connection to %s:%d failed.\n", oursrvaddr, address.port); - } - initprintf(i ? "Retrying...\n" : "Giving up connection attempt.\n"); - } - - Bfree(oursrvaddr); - Net_Disconnect(); -} - -void Net_Disconnect(void) +static void Net_Disconnect(void) { if (g_netClient) { @@ -316,7 +1602,7 @@ void Net_Disconnect(void) if (g_netClientPeer) enet_peer_disconnect_later(g_netClientPeer, 0); - while (enet_host_service(g_netClient, & event, 3000) > 0) + while (enet_host_service(g_netClient, &event, 3000) > 0) { switch (event.type) { @@ -344,13 +1630,14 @@ void Net_Disconnect(void) if (g_netServer) { - int32_t i; + int32_t peerIndex; ENetEvent event; - for (i=0; i<(signed)g_netServer->peerCount; i++) - enet_peer_disconnect_later(&g_netServer->peers[i], DISC_SERVER_QUIT); - - while (enet_host_service(g_netServer, & event, 3000) > 0) + 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) { @@ -359,7 +1646,9 @@ void Net_Disconnect(void) case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_DISCONNECT: if (event.packet) + { enet_packet_destroy(event.packet); + } break; } } @@ -368,7 +1657,8 @@ void Net_Disconnect(void) } } -void Net_ReceiveDisconnect(ENetEvent *event) +// Only clients run this +static void Net_ReceiveDisconnect(ENetEvent *event) { g_netDisconnect = 1; numplayers = g_mostConcurrentPlayers = ud.multimode = 1; @@ -404,289 +1694,347 @@ void Net_ReceiveDisconnect(ENetEvent *event) } } -//////////////////////////////////////////////////////////////////////////////// -// Packet Handlers - -#endif - -void Net_GetPackets(void) +static void Net_SendAcknowledge(ENetPeer *client) { - timerUpdate(); - MUSIC_Update(); + if (!g_netServer) + return; - G_HandleSpecialKeys(); + tempnetbuf[0] = PACKET_ACK; + tempnetbuf[1] = myconnectindex; - if (g_netDisconnect) + 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) { - Net_Disconnect(); - g_netDisconnect = 0; + 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 (g_gameQuit) - G_GameExit(" "); + 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_HandleClientPackets(); - } - else if (g_netClient) - { - Net_HandleServerPackets(); + Net_SendMapVoteCancel(0); } + + voting = -1; } -#ifndef NETCODE_DISABLE - -void Net_HandleClientPackets(void) +static void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver) { - ENetEvent event; + uint32_t byteIndex, j; + int32_t other = pbuf[packbufleng]; - // 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) + for (byteIndex = 1; pbuf[byteIndex]; byteIndex++) { - const intptr_t playeridx = (intptr_t)event.peer->data; + g_player[other].user_name[byteIndex - 1] = pbuf[byteIndex]; + } - if (playeridx < 0 || playeridx >= MAXPLAYERS) - { - enet_peer_disconnect_later(event.peer, DISC_INVALID); - buildprint("Invalid player id (", playeridx, ") from client.\n"); - continue; - } + g_player[other].user_name[byteIndex - 1] = 0; + byteIndex++; - switch (event.type) - { - case ENET_EVENT_TYPE_CONNECT: - { - char ipaddr[32]; + 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++]; - enet_address_get_host_ip(&event.peer->address, ipaddr, sizeof(ipaddr)); + for (j = byteIndex; byteIndex - j < 10; byteIndex++) + { + g_player[other].wchoice[byteIndex - j] = pbuf[byteIndex]; + } - initprintf("A new client connected from %s:%u.\n", ipaddr, event.peer->address.port); - - Net_SendAcknowledge(event.peer); - break; - } - - case ENET_EVENT_TYPE_RECEIVE: - /* - initprintf ("A packet of length %u containing %s was received from player %d on channel %u.\n", - event.packet -> dataLength, - event.packet -> data, - event.peer -> data, - event.channelID); - */ - Net_ParseClientPacket(&event); - // broadcast takes care of enet_packet_destroy itself - // we set the state to disconnected so enet_host_broadcast - // doesn't send the player back his own packets - if ((event.channelID == CHAN_GAMESTATE && event.packet->data[0] > PACKET_BROADCAST) - || event.channelID == CHAN_CHAT) - { - const ENetPacket *pak = event.packet; - - event.peer->state = ENET_PEER_STATE_DISCONNECTED; - enet_host_broadcast(g_netServer, event.channelID, - enet_packet_create(pak->data, pak->dataLength, pak->flags&ENET_PACKET_FLAG_RELIABLE)); - event.peer->state = ENET_PEER_STATE_CONNECTED; - } - - enet_packet_destroy(event.packet); - g_player[playeridx].ping = (event.peer->lastRoundTripTime + event.peer->roundTripTime)/2; - break; - - case ENET_EVENT_TYPE_DISCONNECT: - numplayers--; - ud.multimode--; - - P_RemovePlayer(playeridx); - - 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, 6, ENET_PACKET_FLAG_RELIABLE)); - - initprintf("%s disconnected.\n", g_player[playeridx].user_name); - event.peer->data = NULL; - break; - - default: - break; - } + if (fromserver) + { + g_player[other].playerquitflag = 1; } } -void Net_HandleServerPackets(void) +static void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng) { - ENetEvent event; + 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. - enet_host_service(g_netClient, NULL, 0); - - while (enet_host_check_events(g_netClient, &event) > 0) + Bstrcpy(boardfilename, (char *)pbuf + 1); + boardfilename[packbufleng - 1] = 0; + Bcorrectfilename(boardfilename, 0); + if (boardfilename[0] != 0) { - if (event.type == ENET_EVENT_TYPE_DISCONNECT) + int32_t i; + if ((i = kopen4loadfrommod(boardfilename, 0)) < 0) { - Net_ReceiveDisconnect(&event); + Bmemset(boardfilename, 0, sizeof(boardfilename)); + Net_SendUserMapName(); } - else if (event.type == ENET_EVENT_TYPE_RECEIVE) + else { - Net_ParseServerPacket(&event); + kclose(i); } - - enet_packet_destroy(event.packet); } + + if (ud.m_level_number == 7 && ud.m_volume_number == 0 && boardfilename[0] == 0) + ud.m_level_number = 0; } -void Net_ParseClientPacket(ENetEvent *event) +static void Net_ExtractNewGame(newgame_t *newgame, int32_t menuonly) { - uint8_t *pbuf = event->packet->data; - int32_t packbufleng = event->packet->dataLength; - int16_t j; - int32_t other = pbuf[--packbufleng]; + 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 0 - initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng); -#endif - switch (pbuf[0]) + if (!menuonly) { - case PACKET_SLAVE_TO_MASTER: //[1] (receive slave sync buffer) - Net_ReceiveClientUpdate(event); - break; - - case PACKET_PLAYER_READY: - - if (other == 0) - { - break; - } - - j = g_player[other].ps->i; - Bmemcpy(g_player[other].ps, g_player[0].ps, sizeof(DukePlayer_t)); - - g_player[other].ps->i = j; - changespritestat(j, STAT_PLAYER); - - g_player[other].ps->last_extra = sprite[g_player[other].ps->i].extra = g_player[other].ps->max_player_health; - sprite[g_player[other].ps->i].cstat = 1+256; - actor[g_player[other].ps->i].t_data[2] = actor[g_player[other].ps->i].t_data[3] = actor[g_player[other].ps->i].t_data[4] = 0; - - P_ResetMultiPlayer(other); - Net_SpawnPlayer(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, 2, ENET_PACKET_FLAG_RELIABLE)); - } - g_player[other].pingcnt++; - break; - - case PACKET_AUTH: - Net_ReceiveChallenge(pbuf, packbufleng, event); - break; - - default: - Net_ParsePacketCommon(pbuf, packbufleng, 0); - break; + 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; } } -void Net_ParseServerPacket(ENetEvent *event) +static void Net_ReceiveMapVoteInitiate(uint8_t *pbuf) { - uint8_t *pbuf = event->packet->data; - int32_t packbufleng = event->packet->dataLength; - // input_t *nsyn; + int32_t playerIndex; - --packbufleng; // int32_t other = pbuf[--packbufleng]; + Bmemcpy(&pendingnewgame, pbuf, sizeof(newgame_t)); + Net_ExtractNewGame(&pendingnewgame, 1); -#if 0 - initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng); -#endif - switch (pbuf[0]) + 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--) { - case PACKET_MASTER_TO_SLAVE: - - if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) - { - return; - } - - Net_ReceiveServerUpdate(event); - break; - - case PACKET_MAP_STREAM: - - if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) - return; - - Net_ReceiveMapUpdate(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_ResetMultiPlayer(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; - - default: - Net_ParsePacketCommon(pbuf, packbufleng, 1); - break; + g_player[playerIndex].vote = 0; + g_player[playerIndex].gotvote = 0; } + + g_player[voting].gotvote = g_player[voting].vote = 1; } -void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpacketp) + +static void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpacketp) { switch (pbuf[0]) { @@ -720,33 +2068,211 @@ void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpac } } -//////////////////////////////////////////////////////////////////////////////// -// Acknowledgement Packets -void Net_SendAcknowledge(ENetPeer *client) +static void Net_ParseClientPacket(ENetEvent *event) { - if (!g_netServer) + 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; + } - tempnetbuf[0] = PACKET_ACK; - tempnetbuf[1] = myconnectindex; + 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; - enet_peer_send(client, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 2, ENET_PACKET_FLAG_RELIABLE)); + ticrandomseed = serverupdate.seed; + + for (uint32_t 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; + } } -void Net_ReceiveAcknowledge(uint8_t *pbuf, int32_t packbufleng) -{ - UNREFERENCED_PARAMETER(pbuf); // remove when this variable is used - UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used - - Net_SendChallenge(); -} - -//////////////////////////////////////////////////////////////////////////////// -// Challenge Packets - // sends the version and a simple crc32 of the current password, all verified by the server before the connection can continue -void Net_SendChallenge() +static void Net_SendChallenge() { if (!g_netClientPeer) { @@ -760,1181 +2286,25 @@ void Net_SendChallenge() tempnetbuf[9] = myconnectindex; enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 10, ENET_PACKET_FLAG_RELIABLE)); + + Dbg_PacketSent(PACKET_AUTH); } -void Net_ReceiveChallenge(uint8_t *pbuf, int32_t packbufleng, ENetEvent *event) + +static void Net_ReceiveAcknowledge(uint8_t *pbuf, int32_t packbufleng) { - 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(pbuf); // remove when this variable is used + UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used - 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); + Net_SendChallenge(); } -//////////////////////////////////////////////////////////////////////////////// -// Num Players Packets -void Net_SendNewPlayer(int32_t newplayerindex) +// client only +static void Net_ReceiveNewGame(ENetEvent *event) { - 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, 7, ENET_PACKET_FLAG_RELIABLE)); -} + ClientPlayerReady = 0; -void Net_ReceiveNewPlayer(uint8_t *pbuf, int32_t packbufleng) -{ - int32_t i; - - UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used - - numplayers = pbuf[1]; - g_mostConcurrentPlayers = pbuf[2]; - ud.multimode = pbuf[3]; - if (pbuf[4]) // ID of new player - { - g_player[pbuf[4]].playerquitflag = 1; - - if (!g_player[pbuf[4]].ps) - { - g_player[pbuf[4]].ps = (DukePlayer_t *) Xcalloc(1,sizeof(DukePlayer_t)); - } - if (!g_player[pbuf[4]].inputBits) - { - g_player[pbuf[4]].inputBits = (input_t *) Xcalloc(1,sizeof(input_t)); - } - } - -#ifndef NETCODE_DISABLE - if (pbuf[5] == NET_DEDICATED_SERVER) - { - g_networkMode = NET_DEDICATED_CLIENT; - } -#endif - - for (i=0; iaim_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; - - if (g_netClient) - { - enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE)); - } - else if (g_netServer) - { - enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE)); - } -} - -void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver) -{ - uint32_t i, j; - int32_t other = pbuf[packbufleng]; - - for (i=1; pbuf[i]; i++) - { - g_player[other].user_name[i-1] = pbuf[i]; - } - - g_player[other].user_name[i-1] = 0; - i++; - - g_player[other].ps->aim_mode = pbuf[i++]; - g_player[other].ps->auto_aim = pbuf[i++]; - g_player[other].ps->weaponswitch = pbuf[i++]; - g_player[other].ps->palookup = g_player[other].pcolor = pbuf[i++]; - g_player[other].pteam = pbuf[i++]; - - for (j=i; i-j<10; i++) - { - g_player[other].wchoice[i-j] = pbuf[i]; - } - - if (fromserver) - { - g_player[other].playerquitflag = 1; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Map Name Packets - -void Net_SendUserMapName(void) -{ - int32_t j; - - if (numplayers < 2) - return; - - packbuf[0] = PACKET_USER_MAP; - - Bcorrectfilename(boardfilename,0); - - // user map name is sent with a NUL at the end - j = Bstrlen(boardfilename)+1; - Bmemcpy(&packbuf[1], boardfilename, j); - j++; - - packbuf[j++] = myconnectindex; - - if (g_netClient) - { - enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(packbuf, j, ENET_PACKET_FLAG_RELIABLE)); - } - else if (g_netServer) - { - enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(packbuf, j, ENET_PACKET_FLAG_RELIABLE)); - } -} - -void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng) -{ - int32_t i; - - Bstrcpy(boardfilename,(char *)pbuf+1); - boardfilename[packbufleng-1] = 0; - Bcorrectfilename(boardfilename,0); - if (boardfilename[0] != 0) - { - if ((i = kopen4loadfrommod(boardfilename,0)) < 0) - { - Bmemset(boardfilename,0,sizeof(boardfilename)); - Net_SendUserMapName(); - } - else kclose(i); - } - - if (ud.m_level_number == 7 && ud.m_volume_number == 0 && boardfilename[0] == 0) - ud.m_level_number = 0; -} - -//////////////////////////////////////////////////////////////////////////////// -// Map Update Packets - -netmapstate_t *Net_GetRevision(uint8_t revision, uint8_t cancreate) -{ - assert(revision < NET_REVISIONS); - - if (revision == 0) - { - return &g_mapStartState; - } - - if (cancreate && g_mapStateHistory[revision] == NULL) - { - g_mapStateHistory[revision] = (netmapstate_t *) Xcalloc(1, sizeof(netmapstate_t)); - Bmemset(g_mapStateHistory[revision], 0, sizeof(netmapstate_t)); - } - - return g_mapStateHistory[revision]; -} - -void Net_SendMapUpdate(void) -{ - int32_t pi; - // uint32_t numdiff = 0; - uint32_t diffsize = 0; - uint32_t packetsize = 0; - // uint32_t prevMapDiff = g_netMapRevision; - - if (!g_netServer || numplayers < 2) - { - return; - } - - g_netMapRevision++; - if (g_netMapRevision >= NET_REVISIONS) - { - g_netMapRevision = 1; - } - - Net_SaveMapState(Net_GetRevision(g_netMapRevision, 1)); - - // I use this to test server diffs without requiring a client. - //Net_FillMapDiff(prevMapDiff, g_netMapRevision); - - for (pi = 0; pi < (signed) g_netServer->peerCount; pi++) - { - ENetPeer *const currentPeer = &g_netServer->peers[pi]; - const intptr_t playeridx = (intptr_t) currentPeer->data; - - if (playeridx < 0 || playeridx >= MAXPLAYERS) - { - continue; - } - - if (currentPeer->state != ENET_PEER_STATE_CONNECTED || !g_player[playeridx].playerquitflag) - { - continue; - } - - if (g_player[playeridx].revision == g_netMapRevision) - { - continue; - } - - Net_FillMapDiff(g_player[playeridx].revision, g_netMapRevision); - - diffsize = 4 * sizeof(uint32_t); - diffsize += tempMapDiff.numActors * sizeof(netactor_t); - diffsize += tempMapDiff.numToDelete * sizeof(int32_t); - - packetsize = LZ4_compress_default((const char*)&tempMapDiff, (char*)&tempnetbuf[5], diffsize, tempnetbufsize - 5); - - if (packetsize == 0) - return; - - // apply header - tempnetbuf[0] = PACKET_MAP_STREAM; - - // apply uncompressed size - B_BUF32(&tempnetbuf[1], diffsize); - - packetsize += 5; - - //initprintf("update packet size: %d - revision (%d->%d) - num actors: %d\n", packetsize, g_player[playeridx].revision, g_netMapRevision, tempMapDiff.numActors); - - enet_peer_send(currentPeer, CHAN_GAMESTATE, enet_packet_create(tempnetbuf, packetsize, ENET_PACKET_FLAG_RELIABLE)); - } -} - -void Net_ReceiveMapUpdate(ENetEvent *event) -{ - const uint8_t *pktBuf = (uint8_t *) event->packet->data; - uint32_t diffsize = B_UNBUF32(&pktBuf[1]); - LZ4_decompress_safe((const char*)&pktBuf[5], (char*)&tempMapDiff, diffsize, sizeof(netmapdiff_t)); - - Net_RestoreMapState(); - //initprintf("Update packet size: %d - num actors: %d\n", event->packet->dataLength, tempMapDiff.numActors); -} - -//////////////////////////////////////////////////////////////////////////////// -// Map State - -static int Net_CompareActors(const void *actor1, const void *actor2) -{ - return ((netactor_t const *)actor1)->netIndex - ((netactor_t const *)actor2)->netIndex; -} - -void Net_SaveMapState(netmapstate_t *save) -{ - int32_t i; - int32_t statIndex; - - if (save == NULL) - { - return; - } - - save->numActors = 0; - - for (statIndex = 0; g_netStatnums[statIndex] != MAXSTATUS; ++statIndex) - { - i = headspritestat[g_netStatnums[statIndex]]; - for (; i >= 0; i = nextspritestat[i]) - { - if (save->numActors >= NETMAXACTORS) - { - break; - } - - if (Net_IsRelevantSprite(i) && sprite[i].statnum != STAT_NETALLOC) - { - netactor_t *tempActor = &save->actor[save->numActors]; - Net_CopyToNet(i, tempActor); - save->numActors++; - } - } - } - - qsort(save->actor, save->numActors, sizeof(netactor_t), &Net_CompareActors); -} - -void Net_FillMapDiff(uint32_t fromRevision, uint32_t toRevision) -{ - uint32_t fromIndex = 0; - uint32_t toIndex = 0; - netmapstate_t *fromState; - netmapstate_t *toState; - int32_t *deleteBuf; - netactor_t *actorBuf; - - // First check to see if the tempMapDiff is already filled with the diff we want - if (tempMapDiff.fromRevision == fromRevision && tempMapDiff.toRevision == toRevision) - { - return; - } - - tempMapDiff.fromRevision = fromRevision; - tempMapDiff.toRevision = toRevision; - tempMapDiff.numActors = 0; - tempMapDiff.numToDelete = 0; - - actorBuf = (netactor_t *) tempMapDiff.data; - deleteBuf = (int32_t *) tempnetbuf; - - fromState = Net_GetRevision(fromRevision, 0); - toState = Net_GetRevision(toRevision, 0); - - assert(fromState != NULL); - assert(toState != NULL); - - while (fromIndex < fromState->numActors || toIndex < toState->numActors) - { - const int32_t fromNet = fromState->actor[fromIndex].netIndex; - const int32_t toNet = toState->actor[toIndex].netIndex; - const int32_t fromValid = fromIndex < fromState->numActors; - const int32_t toValid = toIndex < toState->numActors; - if (toValid && (!fromValid || fromNet > toNet)) - { - //initprintf("This actor is new: %d - %d\n", toState->actor[toIndex].netIndex, toState->actor[toIndex].sprite.picnum); - - // Add the "to" data. It's a new actor. - memcpy(&actorBuf[tempMapDiff.numActors], &toState->actor[toIndex], sizeof(netactor_t)); - tempMapDiff.numActors++; - toIndex++; - } - else if (fromValid && (!toValid || toNet > fromNet)) - { - //initprintf("This actor is deleted: %d - %d\n", fromState->actor[fromIndex].netIndex, fromState->actor[fromIndex].sprite.picnum); - - // Add the "fromNet" to the list of deleted actors - deleteBuf[tempMapDiff.numToDelete] = fromState->actor[fromIndex].netIndex; - tempMapDiff.numToDelete++; - fromIndex++; - } - else //if (fromNet == toNet) - { - assert(fromNet == toNet); - - if (Net_ActorsAreDifferent(&fromState->actor[fromIndex], &toState->actor[toIndex])) - { - //initprintf("This actor is different: %d - %d\n", toState->actor[toIndex].netIndex, toState->actor[toIndex].sprite.picnum); - - // Add the "to" data. It's changed. - memcpy(&actorBuf[tempMapDiff.numActors], &toState->actor[toIndex], sizeof(netactor_t)); - tempMapDiff.numActors++; - } - - fromIndex++; - toIndex++; - } - } - - if (tempMapDiff.numToDelete > 0) - { - memcpy(&actorBuf[tempMapDiff.numActors], deleteBuf, tempMapDiff.numToDelete * sizeof(int32_t)); - } -} - -void Net_RestoreMapState() -{ - int32_t i; - uint32_t j; - int32_t *deleteBuf; - netactor_t *actorBuf; - - // Make sure we're using a diff from our current revision - if (tempMapDiff.fromRevision != g_player[myconnectindex].revision) - { - return; - } - - g_player[myconnectindex].revision = tempMapDiff.toRevision; - - actorBuf = (netactor_t *) tempMapDiff.data; - - for (j = 0; j < tempMapDiff.numActors; ++j) - { - i = actorBuf[j].netIndex; - /* - if (Net_IsRelevantSprite(i)) - { - initprintf("This actor is different: %d - %d\n", i, actorBuf[j].sprite.picnum); - } - else - { - initprintf("This actor is new: %d - %d\n", i, actorBuf[j].sprite.picnum); - } - */ - Net_CopyFromNet(i, &actorBuf[j]); - } - - deleteBuf = (int32_t *) &actorBuf[tempMapDiff.numActors]; - for (j = 0; j < tempMapDiff.numToDelete; ++j) - { - //initprintf("This actor is deleted: %d - %d\n", deleteBuf[j], sprite[deleteBuf[j]].picnum); - A_DeleteSprite(deleteBuf[j]); - } -} - -void Net_CopyToNet(int32_t i, netactor_t *netactor) -{ - netactor->netIndex = i; - netactor->picnum = actor[i].picnum; - netactor->ang = actor[i].ang; - netactor->extra = actor[i].extra; - netactor->owner = actor[i].owner; - netactor->movflag = actor[i].movflag; - netactor->tempang = actor[i].tempang; - netactor->timetosleep = actor[i].timetosleep; - netactor->flags = actor[i].flags; - netactor->floorz = actor[i].floorz; - netactor->lastv.x = actor[i].lastv.x; - netactor->lastv.y = actor[i].lastv.y; - netactor->lasttransport = actor[i].lasttransport; - netactor->stayput = actor[i].stayput; - netactor->cgg = actor[i].cgg; - netactor->owner = actor[i].owner; - - Bmemcpy(netactor->t_data, actor[i].t_data, 10 * sizeof(int32_t)); - - Bmemcpy(&netactor->sprite, &sprite[i], sizeof(spritetype)); -} - -void Net_CopyFromNet(int32_t i, netactor_t *netactor) -{ - if (netactor->sprite.statnum == STAT_NETALLOC) - { - // Do nothing if it's going to be deleted - return; - } - else if (sprite[i].statnum == STAT_NETALLOC) - { - changespritestat(i, netactor->sprite.statnum); - do_insertsprite_at_headofsect(i, netactor->sprite.sectnum); - } - else - { - // These functions already check to see if the sprite already has the stat/sect value. No need to do it twice. - changespritestat(i, netactor->sprite.statnum); - changespritesect(i, netactor->sprite.sectnum); - } - - actor[i].picnum = netactor->picnum; - actor[i].ang = netactor->ang; - actor[i].extra = netactor->extra; - actor[i].owner = netactor->owner; - actor[i].movflag = netactor->movflag; - actor[i].tempang = netactor->tempang; - actor[i].timetosleep = netactor->timetosleep; - actor[i].flags = netactor->flags; - actor[i].floorz = netactor->floorz; - actor[i].lastv.x = netactor->lastv.x; - actor[i].lastv.y = netactor->lastv.y; - actor[i].lasttransport = netactor->lasttransport; - actor[i].stayput = netactor->stayput; - actor[i].cgg = netactor->cgg; - actor[i].owner = netactor->owner; - - Bmemcpy(actor[i].t_data, netactor->t_data, 10 * sizeof(int32_t)); - - Bmemcpy(&sprite[i], &netactor->sprite, sizeof(spritetype)); -} - -int32_t Net_ActorsAreDifferent(netactor_t *actor1, netactor_t *actor2) -{ - int32_t allDiff; - int32_t finalDiff; - int32_t nonStandableDiff; - - allDiff = - actor1->picnum != actor2->picnum || - actor1->ang != actor2->ang || - actor1->extra != actor2->extra || - actor1->owner != actor2->owner || - actor1->movflag != actor2->movflag || - actor1->tempang != actor2->tempang || - //actor1->timetosleep != actor2->timetosleep || - actor1->flags != actor2->flags || - actor1->floorz != actor2->floorz || - actor1->lastv.x != actor2->lastv.x || - actor1->lastv.y != actor2->lastv.y || - actor1->lasttransport != actor2->lasttransport || - actor1->stayput != actor2->stayput || - //actor1->cgg != actor2->cgg || - - actor1->sprite.owner != actor2->sprite.owner || - actor1->sprite.statnum != actor2->sprite.statnum || - actor1->sprite.sectnum != actor2->sprite.sectnum || - actor1->sprite.picnum != actor2->sprite.picnum || - //actor1->sprite.shade != actor2->sprite.shade || - actor1->sprite.xrepeat != actor2->sprite.xrepeat || - actor1->sprite.yrepeat != actor2->sprite.yrepeat;// || - //actor1->sprite.ang != actor2->sprite.ang || - - nonStandableDiff = - actor1->sprite.x != actor2->sprite.x || - actor1->sprite.y != actor2->sprite.y || - actor1->sprite.z != actor2->sprite.z || - actor1->sprite.xvel != actor2->sprite.xvel || - actor1->sprite.yvel != actor2->sprite.yvel || - actor1->sprite.zvel != actor2->sprite.zvel; - - finalDiff = allDiff || (actor1->sprite.statnum != STAT_STANDABLE && nonStandableDiff); - - return finalDiff; -} - -int32_t Net_IsRelevantSprite(int32_t i) -{ - if (g_netServer == NULL && g_netClient == NULL) - { - return 0; - } - else if (i < 0 || i >= MAXSPRITES) - { - return 0; - } - else - { - return Net_IsRelevantStat(sprite[i].statnum); - } -} - -int32_t Net_IsRelevantStat(int32_t stat) -{ - int32_t statIndex; - - if (g_netServer == NULL && g_netClient == NULL) - { - return 0; - } - - for (statIndex = 0; g_netStatnums[statIndex] != MAXSTATUS; ++statIndex) - { - if (g_netStatnums[statIndex] == stat) - { - return 1; - } - } - - return 0; -} - -int32_t Net_InsertSprite(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 == -1) - { - return -1; - } - - changespritestat(i, stat); - do_insertsprite_at_headofsect(i, sect); - - return i; -} - -void Net_DeleteSprite(int32_t spritenum) -{ - if (sprite[spritenum].statnum == STAT_NETALLOC) - { - return; - } - - changespritestat(spritenum, STAT_NETALLOC); - do_deletespritesect(spritenum); - sprite[spritenum].sectnum = MAXSECTORS; -} - -extern void Gv_RefreshPointers(void); - -//////////////////////////////////////////////////////////////////////////////// -// Player Updates - -void Net_FillPlayerUpdate(playerupdate_t *update, int32_t player) -{ - update->playerindex = player; - - update->pos = g_player[player].ps->pos; - update->opos = g_player[player].ps->opos; - update->vel = g_player[player].ps->vel; - update->ang = g_player[player].ps->q16ang; - update->horiz = g_player[player].ps->q16horiz; - update->horizoff = 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; -} - -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->ang; - g_player[playerindex].ps->q16horiz = update->horiz; - g_player[playerindex].ps->q16horizoff = update->horizoff; - } - - if (type == PACKET_MASTER_TO_SLAVE) - { - g_player[playerindex].ping = update->ping; - g_player[playerindex].ps->dead_flag = update->deadflag; - g_player[playerindex].playerquitflag = update->playerquitflag; - } - - //updatesectorz(g_player[other].ps->pos.x, g_player[other].ps->pos.y, g_player[other].ps->pos.z, &g_player[other].ps->cursectnum); - //changespritesect(g_player[other].ps->i, g_player[other].ps->cursectnum); -} - -//////////////////////////////////////////////////////////////////////////////// -// Server Update Packets - -#pragma pack(push,1) -typedef struct -{ - 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 -{ - 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) - -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.extra = sprite[g_player[i].ps->i].extra; - playerupdate.cstat = sprite[g_player[i].ps->i].cstat; - playerupdate.owner = actor[g_player[i].ps->i].owner; - playerupdate.picnum = actor[g_player[i].ps->i].picnum; - 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; - playerupdate.pal = sprite[g_player[i].ps->i].pal; - - 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, sizeof(serverupdate_t) + (serverupdate.numplayers * sizeof(serverplayerupdate_t)), 0)); -} - -void Net_ReceiveServerUpdate(ENetEvent *event) -{ - int32_t i; - uint8_t *updatebuf; - // int8_t numupdates; - serverupdate_t serverupdate; - serverplayerupdate_t playerupdate; - - if (((event->packet->dataLength - sizeof(serverupdate_t)) % sizeof(serverplayerupdate_t)) != 0) - { - return; - } - - updatebuf = (uint8_t *) event->packet->data; - Bmemcpy(&serverupdate, updatebuf, sizeof(serverupdate_t)); - updatebuf += sizeof(serverupdate_t); - inputfifo[0][0] = serverupdate.nsyn; - ud.pause_on = serverupdate.pause_on; - - ticrandomseed = serverupdate.seed; - - for (i = 0; i < serverupdate.numplayers; ++i) - { - Bmemcpy(&playerupdate, updatebuf, sizeof(serverplayerupdate_t)); - updatebuf += sizeof(serverplayerupdate_t); - - Net_ExtractPlayerUpdate(&playerupdate.player, PACKET_MASTER_TO_SLAVE); - - g_player[i].ps->gotweapon = playerupdate.gotweapon; - sprite[g_player[i].ps->i].extra = playerupdate.extra; - sprite[g_player[i].ps->i].cstat = playerupdate.cstat; - //actor[g_player[i].ps->i].owner = playerupdate.owner; // This makes baby jesus cry - actor[g_player[i].ps->i].picnum = playerupdate.picnum; - g_player[i].ps->kickback_pic = playerupdate.kickback_pic; - Bmemcpy(g_player[i].frags, playerupdate.frags, sizeof(playerupdate.frags)); - Bmemcpy(g_player[i].ps->inv_amount, playerupdate.inv_amount, sizeof(playerupdate.inv_amount)); - Bmemcpy(g_player[i].ps->ammo_amount, playerupdate.ammo_amount, sizeof(playerupdate.ammo_amount)); - - g_player[i].ps->curr_weapon = playerupdate.curr_weapon; - g_player[i].ps->last_weapon = playerupdate.last_weapon; - g_player[i].ps->wantweaponfire = playerupdate.wantweaponfire; - g_player[i].ps->weapon_pos = playerupdate.weapon_pos; - g_player[i].ps->frag_ps = playerupdate.frag_ps; - g_player[i].ps->frag = playerupdate.frag; - g_player[i].ps->fraggedself = playerupdate.fraggedself; - g_player[i].ps->last_extra = playerupdate.last_extra; - g_player[i].ping = playerupdate.ping; - sprite[g_player[i].ps->i].pal = playerupdate.pal; - g_player[i].ps->newowner = playerupdate.newowner; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Client Update Packets - -#pragma pack(push,1) -typedef struct -{ - uint8_t header; - uint32_t revision; - input_t nsyn; - playerupdate_t player; -} clientupdate_t; -#pragma pack(pop) - -void Net_SendClientUpdate(void) -{ - clientupdate_t update; - update.header = PACKET_SLAVE_TO_MASTER; - update.revision = g_player[myconnectindex].revision; - 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)); -} - -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.revision; - inputfifo[0][playeridx] = update.nsyn; - - Net_ExtractPlayerUpdate(&update.player, PACKET_SLAVE_TO_MASTER); -} - -//////////////////////////////////////////////////////////////////////////////// -// Message Packets - -void Net_SendMessage(void) -{ - - if (g_player[myconnectindex].ps->gm&MODE_SENDTOWHOM) - { - int32_t i, j; - if (g_chatPlayer != -1 || ud.multimode < 3) - { - tempbuf[0] = PACKET_MESSAGE; - tempbuf[2] = 0; - recbuf[0] = 0; - - if (ud.multimode < 3) - g_chatPlayer = 2; - - if (typebuf[0] == '/' && Btoupper(typebuf[1]) == 'M' && Btoupper(typebuf[2]) == 'E') - { - Bstrcat(recbuf,"* "); - i = 3, j = Bstrlen(typebuf); - Bstrcpy(tempbuf,typebuf); - while (i < j) - { - typebuf[i-3] = tempbuf[i]; - i++; - } - typebuf[i-3] = '\0'; - Bstrcat(recbuf,g_player[myconnectindex].user_name); - } - else - { - Bstrcat(recbuf,g_player[myconnectindex].user_name); - Bstrcat(recbuf,": "); - } - - Bstrcat(recbuf,"^00"); - Bstrcat(recbuf,typebuf); - j = Bstrlen(recbuf); - recbuf[j] = 0; - Bstrcat(tempbuf+2,recbuf); - - if (g_chatPlayer >= ud.multimode) - { - tempbuf[1] = 255; - tempbuf[j+2] = myconnectindex; - j++; - if (g_netServer) enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(tempbuf, j+2, 0)); - else if (g_netClient) enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(tempbuf, 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 - { -#define MAXCHATLENGTH 120 - EDUKE32_STATIC_ASSERT(MAXCHATLENGTH < TYPEBUFSIZE); - int32_t const hitstate = I_EnterText(typebuf, MAXCHATLENGTH, 0); - - int32_t const y = ud.screen_size > 1 ? (200-58)<<16 : (200-35)<<16; - - int32_t const width = mpgametextsize(typebuf, TEXT_LITERALESCAPE).x; - int32_t const fullwidth = width + textsc((tilesiz[SPINNINGNUKEICON].x<<15)+(2<<16)); - int32_t const text_x = fullwidth >= (320<<16) ? (320<<16) - fullwidth : mpgametext_x; - mpgametext(text_x, y, typebuf, 1, 2|8|16|ROTATESPRITE_FULL16, 0, TEXT_YCENTER|TEXT_LITERALESCAPE); - int32_t const cursor_x = text_x + width + textsc((tilesiz[SPINNINGNUKEICON].x<<14)+(1<<16)); - rotatesprite_fs(cursor_x, y, textsc(32768), 0, SPINNINGNUKEICON+((totalclock>>3)%7), 4-(sintable[(totalclock<<4)&2047]>>11), 0, 2|8); - - if (hitstate == 1) - { - KB_ClearKeyDown(sc_Enter); - if (Bstrlen(typebuf) == 0) - { - g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM); - return; - } - if (ud.automsg) - { - if (SHIFTS_IS_PRESSED) g_chatPlayer = -1; - else g_chatPlayer = ud.multimode; - } - g_player[myconnectindex].ps->gm |= MODE_SENDTOWHOM; - } - else if (hitstate == -1) - g_player[myconnectindex].ps->gm &= ~(MODE_TYPE|MODE_SENDTOWHOM); - else pub = NUMPAGES; - } -} - -void Net_ReceiveMessage(uint8_t *pbuf, int32_t packbufleng) -{ - Bstrncpy(recbuf, (char *)pbuf+2, packbufleng-2); - recbuf[packbufleng-2] = 0; - - G_AddUserQuote(recbuf); - S_PlaySound(EXITMENUSOUND); - - pus = pub = NUMPAGES; -} - -//////////////////////////////////////////////////////////////////////////////// -// New Game Packets - -void Net_StartNewGame() -{ - int32_t i; - - for (TRAVERSE_CONNECT(i)) - { - P_ResetWeapons(i); - P_ResetInventory(i); - g_player[i].revision = 0; - } - - Net_ExtractNewGame(&pendingnewgame, 0); - G_NewGame(ud.volume_number,ud.level_number,ud.player_skill); - ud.coop = ud.m_coop; - - g_netMapRevision = 0; - - if (G_EnterLevel(MODE_GAME)) - { - G_BackToMenu(); - } -} - -void Net_NotifyNewGame() -{ - int32_t i; - 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) - { - i = headspritestat[statIndex]; - for (; i >= 0; i = nextspritestat[i]) - { - numSprites++; - } - } - - // Take half of the leftover sprites and allocate them for the network's nefarious purposes. - numSpritesToNetAlloc = (MAXSPRITES - numSprites) / 2; - for (i = 0; i < numSpritesToNetAlloc; ++i) - { - int32_t newSprite = insertspritestat(STAT_NETALLOC); - sprite[newSprite].sectnum = MAXSECTORS; - Numsprites++; - } - - Net_SaveMapState(&g_mapStartState); -} - -void Net_SendNewGame(int32_t frommenu, ENetPeer *peer) -{ - newgame_t newgame; - - newgame.header = PACKET_NEW_GAME; - Net_FillNewGame(&newgame, frommenu); - - 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)); - } -} - -void Net_ReceiveNewGame(ENetEvent *event) -{ if ((vote_map + vote_episode + voting) != -3) G_AddUserQuote("Vote Succeeded"); @@ -1946,217 +2316,2103 @@ void Net_ReceiveNewGame(ENetEvent *event) if (g_netClientPeer) { - enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(packbuf, 2, ENET_PACKET_FLAG_RELIABLE)); + 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; + ready2send = 1; } -void Net_FillNewGame(newgame_t *newgame, int32_t frommenu) + +static void Net_ReceiveNewPlayer(uint8_t *pbuf, int32_t packbufleng) { - if (frommenu) + 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 { - 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; + g_player[pbuf[4]].playerquitflag = 1; + + if (!g_player[pbuf[4]].ps) + { + g_player[pbuf[4]].ps = (DukePlayer_t *)Xcalloc(1, sizeof(DukePlayer_t)); + } + if (!g_player[pbuf[4]].inputBits) + { + g_player[pbuf[4]].inputBits = (input_t *)Xcalloc(1, sizeof(input_t)); + } } - else + + if (pbuf[5] == NET_DEDICATED_SERVER) { - 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; + 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(); } } -void Net_ExtractNewGame(newgame_t *newgame, int32_t menuonly) +static void Net_ReceivePlayerIndex(uint8_t *pbuf, int32_t packbufleng) { - 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; + UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used - if (!menuonly) + 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]) { - 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; + 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 Vote Packets +// Map Update Packets -void Net_SendMapVoteInitiate(void) + +//Insert a sprite from STAT_NETALLOC +static int32_t Net_DoInsertSprite(int32_t sect, int32_t stat) { - newgame_t newgame; + 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 "net.c: game arrays are now too big to send over the network, please update NETINDEX_BITS to be the right length to store a wall, sprite, and sector index" +#endif + +#define STRUCTINDEX_BITS 8 // hopefully no game structs ever have more than 255 fields.... + +#define DEBUG_CHANGEDINDEXES 1 + +#define FLOAT_INT_BITS 13 + +// remember that the minimum negative number is the sign bit + all zeros +const int32_t cTruncInt_Min = -(1 << (FLOAT_INT_BITS - 1)); +const int32_t cTruncInt_Max = (1 << (FLOAT_INT_BITS - 1)) - 1; + +typedef struct NetBuffer_s +{ + uint8_t *Data; + int32_t Bit; + int32_t ReadCurByte; + int32_t CurSize; + int32_t MaxSize; // set in NetBuffer_Init to the size of the byte array that this struct referrs to in Data +} NetBuffer_t; + +// NOTE: does NOT fill 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)); + + 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; } - voting = myconnectindex; + NetBuffer_t buffer; + NetBuffer_t* bufferPtr = &buffer; + uint8_t* dataStartAddr = packetData + 1; - newgame.header = PACKET_MAP_VOTE_INITIATE; - newgame.connection = myconnectindex; - Net_FillNewGame(&newgame, 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); - enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE)); -} - -void Net_ReceiveMapVoteInitiate(uint8_t *pbuf) -{ - int32_t i; - - 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 (i=MAXPLAYERS-1; i>=0; i--) - { - g_player[i].vote = 0; - g_player[i].gotvote = 0; - } - - g_player[voting].gotvote = g_player[voting].vote = 1; -} - -void Net_SendMapVote(int32_t votefor) -{ - voting = -1; - g_player[myconnectindex].gotvote = 1; - g_player[myconnectindex].vote = votefor; - - tempbuf[0] = PACKET_MAP_VOTE; - tempbuf[1] = myconnectindex; - tempbuf[2] = votefor; - tempbuf[3] = myconnectindex; - - if (g_netClient) - { - enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 4, ENET_PACKET_FLAG_RELIABLE)); - } - else if (g_netServer) - { - enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 4, ENET_PACKET_FLAG_RELIABLE)); - } - - Net_CheckForEnoughVotes(); -} - -void Net_ReceiveMapVote(uint8_t *pbuf) -{ - if (voting == myconnectindex && g_player[(uint8_t)pbuf[1]].gotvote == 0) - { - Bsprintf(tempbuf,"Confirmed vote from %s",g_player[(uint8_t)pbuf[1]].user_name); - G_AddUserQuote(tempbuf); - } - - if (!g_netServer) + if (!ClientPlayerReady) { return; } - g_player[(uint8_t)pbuf[1]].gotvote = 1; - g_player[(uint8_t)pbuf[1]].vote = pbuf[2]; - Net_CheckForEnoughVotes(); -} + NetBuffer_Init(bufferPtr, dataStartAddr, MAX_WORLDBUFFER); -void Net_CheckForEnoughVotes() -{ - int32_t i; - int32_t requiredvotes; - int32_t numfor, numagainst; + bufferPtr->CurSize = packetSize - 1; - // Only the server can decide map changes - if (!g_netServer || numplayers <= 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 there are just two players, both of them deserve a vote - if (numplayers == 2) + if (from_IsInitialState) { - requiredvotes = 2; + // 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 { - // 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++; - } + fromMapState = &g_mapStateHistory[packetFromRevisionNumber % NET_REVISIONS]; } - numfor = numagainst = 0; - for (i=0; irevisionNumber = 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) { - if (g_player[i].gotvote) - { - if (g_player[i].vote) - { - numfor++; - } - else - { - numagainst++; - } - } + // we should be able to recover from this + OSD_Printf("Net_SendMapUpdates(): [Note] Map state revision number overflow."); + return cStartingRevisionIndex; } - if (numfor >= requiredvotes) + return (currentNumber + 1); +} + +// --------------------------------------------------------------------------------------------------------------- +// Externally accessible functions +// --------------------------------------------------------------------------------------------------------------- + + + +void Net_WaitForInitialSnapshot() +{ + while (g_netMapRevisionNumber < cStartingRevisionIndex) { - Net_StartNewGame(); - Net_SendNewGame(1, NULL); - } - else if (numagainst >= requiredvotes || (numfor + numagainst) == numplayers) - { - Net_SendMapVoteCancel(1); + 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) { @@ -2177,49 +4433,800 @@ void Net_SendMapVoteCancel(int32_t failed) voting = -1; + Dbg_PacketSent(PACKET_MAP_VOTE_CANCEL); + if (g_netClient) { - enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 2, ENET_PACKET_FLAG_RELIABLE)); + 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, 2, ENET_PACKET_FLAG_RELIABLE)); + enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 2, ENET_PACKET_FLAG_RELIABLE)); } } -void Net_ReceiveMapVoteCancel(uint8_t *pbuf) +void Net_SendMapVote(int32_t votefor) { - // int32_t numvotes; + voting = -1; + g_player[myconnectindex].gotvote = 1; + g_player[myconnectindex].vote = votefor; - // Ignore if we're not voting - if (voting == -1) + 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; } - // Ignore cancellations from clients that did not initiate the map vote - if (voting != pbuf[1] && voting != myconnectindex) + 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; } - if (voting == myconnectindex || voting != pbuf[1]) + // don't store client revisions if we don't even have a server revision + if(g_netMapRevisionNumber < cStartingRevisionIndex) { - Bsprintf(tempbuf,"Vote Failed"); - } - else if (voting == pbuf[1]) - { - Bsprintf(tempbuf,"%s^00 has canceled the vote",g_player[voting].user_name); + return; } - G_AddUserQuote(tempbuf); + 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); + } + + // 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); + Bfree(oursrvaddr); + return; + } + else + { + /* Either the 5 seconds are up or a disconnect event was */ + /* received. Reset the peer in the event the 5 seconds */ + /* had run out without any significant event. */ + enet_peer_reset(g_netClientPeer); + initprintf("Connection to %s:%d failed.\n", oursrvaddr, address.port); + } + initprintf(connectCount ? "Retrying...\n" : "Giving up connection attempt.\n"); + } + + // [75] note: it only gets here if there was an error + Bfree(oursrvaddr); + Net_Disconnect(); +} + +///< summary> +/// Record the current state of the game arrays to the initial snapshot +/// +void Net_AddWorldToInitialSnapshot() +{ + Net_AddWorldToSnapshot(&g_mapStartState); +} + +void Net_GetPackets(void) +{ + timerUpdate(); + MUSIC_Update(); + S_Update(); + + G_HandleSpecialKeys(); + + if (g_netDisconnect) + { + Net_Disconnect(); + g_netDisconnect = 0; + + if (g_gameQuit) + G_GameExit(" "); + + return; + } if (g_netServer) { - Net_SendMapVoteCancel(0); + Net_HandleClientPackets(); + } + else if (g_netClient) + { + Net_HandleServerPackets(); } - - voting = -1; } -#endif // !defined NETCODE_DISABLE +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_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 + +//------------------------------------------------------------------------------------------------- diff --git a/source/duke3d/src/net.h b/source/duke3d/src/net.h index c4de1cada..789e52cef 100644 --- a/source/duke3d/src/net.h +++ b/source/duke3d/src/net.h @@ -1,6 +1,6 @@ //------------------------------------------------------------------------- /* -Copyright (C) 2010 EDuke32 developers and contributors +Copyright (C) 2017 EDuke32 developers and contributors This file is part of EDuke32. @@ -23,10 +23,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #ifndef netplay_h_ #define netplay_h_ -#ifdef _WIN32 -// include this before enet does -# define NEED_WINSOCK2_H -# include "windows_inc.h" +// As of Nov. of 2018, the version of gcc that the Makefile is using is too old for [[maybe_unused]], +// but __attribute__ is GNU C only. +#ifdef __GNUC__ +#define EDUKE32_UNUSED __attribute__((unused)) +#else +#define EDUKE32_UNUSED #endif #include "enet/enet.h" @@ -41,16 +43,8 @@ extern char g_netPassword[32]; extern int32_t g_netDisconnect; extern int32_t g_netPlayersWaiting; extern enet_uint16 g_netPort; -#ifndef NETCODE_DISABLE extern int32_t g_networkMode; -#else -#define g_networkMode 0 -#endif extern int32_t g_netIndex; -extern int32_t lastsectupdate[MAXSECTORS]; -extern int32_t lastupdate[MAXSPRITES]; -extern int32_t lastwallupdate[MAXWALLS]; -extern int16_t g_netStatnums[]; #define NET_REVISIONS 64 @@ -63,6 +57,7 @@ enum netchan_t CHAN_MAX }; + enum DukePacket_t { PACKET_MASTER_TO_SLAVE, @@ -77,7 +72,7 @@ enum DukePacket_t PACKET_AUTH, PACKET_PLAYER_PING, PACKET_PLAYER_READY, - PACKET_MAP_STREAM, + PACKET_WORLD_UPDATE, //[75] // any packet with an ID higher than PACKET_BROADCAST is rebroadcast by server // so hacked clients can't create fake server packets and get the server to @@ -86,7 +81,7 @@ enum DukePacket_t // channel the packet was broadcast on PACKET_BROADCAST, - PACKET_NEW_GAME, + PACKET_NEW_GAME, // [S->C] PACKET_RTS, PACKET_CLIENT_INFO, PACKET_MESSAGE, @@ -97,6 +92,8 @@ enum DukePacket_t PACKET_MAP_VOTE_CANCEL, }; + + enum netdisconnect_t { DISC_BAD_PASSWORD = 1, @@ -116,41 +113,101 @@ enum netmode_t NET_DEDICATED_SERVER }; -#define NETMAXACTORS 1024 + +//[75] + +typedef struct netWall_s +{ + uint16_t netIndex; + + int32_t x, + y, + point2, + nextwall, + nextsector, + + cstat, + picnum, + overpicnum, + shade, + pal, + + xrepeat, + yrepeat, + xpanning, + ypanning, + lotag, + + hitag, + extra; + +} netWall_t; + +// sector struct with all 32 bit entries +typedef struct netSector_s +{ + uint16_t netIndex; + + int32_t wallptr, + wallnum, + ceilingz, + floorz, + ceilingstat, + + floorstat, + ceilingpicnum, + ceilingheinum, + ceilingshade, + ceilingpal, + + ceilingxpanning, + ceilingypanning, + floorpicnum, + floorheinum, + floorshade, + + floorpal, + floorxpanning, + floorypanning, + visibility, + fogpal, + + lotag, + hitag, + extra; +} netSector_t; + +const uint64_t cSnapshotMemUsage = NET_REVISIONS * ( + (sizeof(netWall_t) * MAXWALLS) + + (sizeof(netSector_t) * MAXSECTORS) + + (sizeof(netactor_t) * MAXSPRITES) + ); + #pragma pack(push,1) -typedef struct +typedef struct netmapstate_s { - - uint32_t numActors; - netactor_t actor[NETMAXACTORS]; + uint32_t revisionNumber; + int32_t maxActorIndex; + netactor_t actor[MAXSPRITES]; + netWall_t wall[MAXWALLS]; + netSector_t sector[MAXSECTORS]; } netmapstate_t; -typedef struct -{ - - uint32_t numActors; - uint32_t numToDelete; - uint32_t fromRevision; - uint32_t toRevision; - char data[MAXSPRITES *sizeof(netactor_t)]; - -} netmapdiff_t; - -extern netmapstate_t *g_multiMapState[MAXPLAYERS]; -extern netmapstate_t *g_multiMapRevisions[NET_REVISIONS]; #pragma pack(pop) #pragma pack(push,1) -typedef struct +typedef struct playerupdate_s { vec3_t pos; vec3_t opos; vec3_t vel; - int16_t ang; - int16_t horiz; - int16_t horizoff; + + fix16_t q16ang; + fix16_t q16horiz; + fix16_t q16horizoff; + int16_t ping; int16_t playerindex; int16_t deadflag; @@ -159,7 +216,7 @@ typedef struct #pragma pack(pop) #pragma pack(push,1) -typedef struct +typedef struct newgame_s { int8_t header; int8_t connection; @@ -177,182 +234,120 @@ typedef struct } newgame_t; #pragma pack(pop) + + extern newgame_t pendingnewgame; #ifndef NETCODE_DISABLE // Connect/Disconnect void Net_Connect(const char *srvaddr); -void Net_Disconnect(void); -void Net_ReceiveDisconnect(ENetEvent *event); // Packet Handlers #endif void Net_GetPackets(void); #ifndef NETCODE_DISABLE -void Net_HandleServerPackets(void); -void Net_HandleClientPackets(void); -void Net_ParseClientPacket(ENetEvent *event); -void Net_ParseServerPacket(ENetEvent *event); -void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpacketp); - -void Net_SendAcknowledge(ENetPeer *client); -void Net_ReceiveAcknowledge(uint8_t *pbuf, int32_t packbufleng); - -void Net_SendChallenge(); -void Net_ReceiveChallenge(uint8_t *pbuf, int32_t packbufleng, ENetEvent *event); - -void Net_SendNewPlayer(int32_t newplayerindex); -void Net_ReceiveNewPlayer(uint8_t *pbuf, int32_t packbufleng); - -void Net_SendPlayerIndex(int32_t index, ENetPeer *peer); -void Net_ReceivePlayerIndex(uint8_t *pbuf, int32_t packbufleng); void Net_SendClientInfo(void); -void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver); void Net_SendUserMapName(void); -void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng); - -netmapstate_t *Net_GetRevision(uint8_t revision, uint8_t cancreate); void Net_SendMapUpdate(void); -void Net_ReceiveMapUpdate(ENetEvent *event); -void Net_FillMapDiff(uint32_t fromRevision, uint32_t toRevision); -void Net_SaveMapState(netmapstate_t *save); -void Net_RestoreMapState(); - -void Net_CopyToNet(int32_t i, netactor_t *netactor); -void Net_CopyFromNet(int32_t i, netactor_t *netactor); -int32_t Net_ActorsAreDifferent(netactor_t *actor1, netactor_t *actor2); -int32_t Net_IsRelevantSprite(int32_t i); -int32_t Net_IsRelevantStat(int32_t stat); int32_t Net_InsertSprite(int32_t sect, int32_t stat); void Net_DeleteSprite(int32_t spritenum); -void Net_FillPlayerUpdate(playerupdate_t *update, int32_t player); -void Net_ExtractPlayerUpdate(playerupdate_t *update, int32_t type); - void Net_SendServerUpdates(void); -void Net_ReceiveServerUpdate(ENetEvent *event); void Net_SendClientUpdate(void); -void Net_ReceiveClientUpdate(ENetEvent *event); void Net_SendMessage(void); -void Net_ReceiveMessage(uint8_t *pbuf, int32_t packbufleng); void Net_StartNewGame(); void Net_NotifyNewGame(); void Net_SendNewGame(int32_t frommenu, ENetPeer *peer); -void Net_ReceiveNewGame(ENetEvent *event); void Net_FillNewGame(newgame_t *newgame, int32_t frommenu); -void Net_ExtractNewGame(newgame_t *newgame, int32_t menuonly); void Net_SendMapVoteInitiate(void); -void Net_ReceiveMapVoteInitiate(uint8_t *pbuf); void Net_SendMapVote(int32_t votefor); -void Net_ReceiveMapVote(uint8_t *pbuf); -void Net_CheckForEnoughVotes(); void Net_SendMapVoteCancel(int32_t failed); -void Net_ReceiveMapVoteCancel(uint8_t *pbuf); + +void Net_StoreClientState(void); ////////// void Net_ResetPrediction(void); void Net_SpawnPlayer(int32_t player); -void Net_SyncPlayer(ENetEvent *event); void Net_WaitForServer(void); void faketimerhandler(void); +void Net_InitMapStateHistory(); +void Net_AddWorldToInitialSnapshot(); + +// Debugging +int32_t Dbg_PacketSent(enum DukePacket_t iPacketType); + +void DumpMapStateHistory(); + +void Net_WaitForInitialSnapshot(); + #else -/* NETCODE_ENABLE is not defined */ +// note: don't include faketimerhandler in this + +#define Net_GetPackets(...) ((void)0) -// Connect/Disconnect #define Net_Connect(...) ((void)0) -#define Net_Disconnect(...) ((void)0) -#define Net_ReceiveDisconnect(...) ((void)0) - -// Packet Handlers -#define Net_HandleServerPackets(...) ((void)0) -#define Net_HandleClientPackets(...) ((void)0) -#define Net_ParseClientPacket(...) ((void)0) -#define Net_ParseServerPacket(...) ((void)0) -#define Net_ParsePacketCommon(...) ((void)0) - -#define Net_SendAcknowledge(...) ((void)0) -#define Net_ReceiveAcknowledge(...) ((void)0) - -#define Net_SendChallenge(...) ((void)0) -#define Net_ReceiveChallenge(...) ((void)0) - -#define Net_SendNewPlayer(...) ((void)0) -#define Net_ReceiveNewPlayer(...) ((void)0) - -#define Net_SendPlayerIndex(...) ((void)0) -#define Net_ReceivePlayerIndex(...) ((void)0) #define Net_SendClientInfo(...) ((void)0) -#define Net_ReceiveClientInfo(...) ((void)0) #define Net_SendUserMapName(...) ((void)0) -#define Net_ReceiveUserMapName(...) ((void)0) #define Net_SendClientSync(...) ((void)0) #define Net_ReceiveClientSync(...) ((void)0) -#define Net_SendMapUpdate(...) ((void)0) +#define Net_SendMapUpdates(...) ((void)0) #define Net_ReceiveMapUpdate(...) ((void)0) -#define Net_FillPlayerUpdate(...) ((void)0) -#define Net_ExtractPlayerUpdate(...) ((void)0) - #define Net_SendServerUpdates(...) ((void)0) -#define Net_ReceiveServerUpdate(...) ((void)0) #define Net_SendClientUpdate(...) ((void)0) -#define Net_ReceiveClientUpdate(...) ((void)0) #define Net_SendMessage(...) ((void)0) -#define Net_ReceiveMessage(...) ((void)0) #define Net_StartNewGame(...) ((void)0) #define Net_SendNewGame(...) ((void)0) -#define Net_ReceiveNewGame(...) ((void)0) #define Net_FillNewGame(...) ((void)0) -#define Net_ExtractNewGame(...) ((void)0) #define Net_SendMapVoteInitiate(...) ((void)0) -#define Net_ReceiveMapVoteInitiate(...) ((void)0) #define Net_SendMapVote(...) ((void)0) -#define Net_ReceiveMapVote(...) ((void)0) -#define Net_CheckForEnoughVotes(...) ((void)0) #define Net_SendMapVoteCancel(...) ((void)0) -#define Net_ReceiveMapVoteCancel(...) ((void)0) - -////////// #define Net_ResetPrediction(...) ((void)0) #define Net_RestoreMapState(...) ((void)0) -#define Net_SyncPlayer(...) ((void)0) #define Net_WaitForServer(...) ((void)0) -#define Net_ActorsAreDifferent(...) 0 -#define Net_IsRelevantSprite(...) 0 -#define Net_IsRelevantStat(...) 0 -#define Net_InsertSprite(...) 0 +#define Net_InsertSprite(...) ((void)0) #define Net_DeleteSprite(...) ((void)0) #define Net_NotifyNewGame(...) ((void)0) +#define Net_WaitForInitialSnapshot(...) ((void)0) +#define Net_SendMapUpdate(...) ((void)0) +#define Net_StoreClientState(...) ((void)0) +#define Net_InitMapStateHistory(...) ((void)0) +#define Net_AddWorldToInitialSnapshot(...) ((void)0) +#define DumpMapStateHistory(...) ((void)0) + + + + #endif #endif // netplay_h_ diff --git a/source/duke3d/src/osdcmds.cpp b/source/duke3d/src/osdcmds.cpp index 0731e2d42..2ba65d884 100644 --- a/source/duke3d/src/osdcmds.cpp +++ b/source/duke3d/src/osdcmds.cpp @@ -1502,6 +1502,36 @@ static int osdcmd_cvar_set_multi(osdcmdptr_t parm) return r; } +#ifndef NETCODE_DISABLE +static int32_t osdcmd_dumpmapstate(osdfuncparm_t const * const) +{ + // this command takes no parameters + + DumpMapStateHistory(); + + return OSDCMD_OK; +} + +static int32_t osdcmd_playerinfo(osdfuncparm_t const * const) +{ + OSD_Printf("Your player index is %d.\n", myconnectindex); + + for(int32_t playerIndex = 0; playerIndex < MAXPLAYERS; playerIndex++) + { + if(g_player[playerIndex].ps == nullptr) + { + OSD_Printf("g_player[%d]: ps unallocated.\n", playerIndex); + } + else + { + OSD_Printf("g_player[%d]: ps->i is %d.\n", playerIndex, g_player[playerIndex].ps->i); + } + } + + return OSDCMD_OK; +} +#endif + int32_t registerosdcommands(void) { static osdcvardata_t cvars_game[] = @@ -1745,6 +1775,12 @@ int32_t registerosdcommands(void) #ifdef USE_OPENGL baselayer_osdcmd_vidmode_func = osdcmd_vidmode; #endif + +#ifndef NETCODE_DISABLE + OSD_RegisterFunction("dumpmapstates", "Dumps current snapshots to CL/Srv_MapStates.bin", osdcmd_dumpmapstate); + OSD_RegisterFunction("playerinfo", "Prints information about the current player", osdcmd_playerinfo); +#endif + return 0; } diff --git a/source/duke3d/src/player.cpp b/source/duke3d/src/player.cpp index ef1f03cb0..c7ee03f3b 100644 --- a/source/duke3d/src/player.cpp +++ b/source/duke3d/src/player.cpp @@ -3674,6 +3674,7 @@ void P_FragPlayer(int playerNum) #ifndef NETCODE_DISABLE if (g_netServer) { + // this packet might not be needed anymore with the new snapshot code packbuf[0] = PACKET_FRAG; packbuf[1] = playerNum; packbuf[2] = pPlayer->frag_ps; @@ -3681,7 +3682,7 @@ void P_FragPlayer(int playerNum) B_BUF32(&packbuf[4], ticrandomseed); packbuf[8] = myconnectindex; - enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(packbuf, 9, ENET_PACKET_FLAG_RELIABLE)); + enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 9, ENET_PACKET_FLAG_RELIABLE)); } #endif } @@ -5473,9 +5474,9 @@ HORIZONLY:; break; case APLAYER__STATIC: { - int playerNum = P_Get(pPlayer->actorsqu); - P_QuickKill(g_player[playerNum].ps); - g_player[playerNum].ps->frag_ps = playerNum; + const int playerSquished = P_Get(pPlayer->actorsqu); + P_QuickKill(g_player[playerSquished].ps); + g_player[playerSquished].ps->frag_ps = playerNum; break; } default: diff --git a/source/duke3d/src/player.h b/source/duke3d/src/player.h index 53b3af92a..e256a922a 100644 --- a/source/duke3d/src/player.h +++ b/source/duke3d/src/player.h @@ -223,7 +223,9 @@ typedef struct { // NOTE: wchoice[HANDREMOTE_WEAPON .. MAX_WEAPONS-1] unused uint8_t frags[MAXPLAYERS], wchoice[MAX_WEAPONS]; - char vote, gotvote, pingcnt, playerquitflag, ready; + + char vote, gotvote, pingcnt, playerquitflag, + ready; // currently unused. May be used later to indicate that a player has pressed use on intermission to indicate they are ready to go on to the next map char user_name[32]; uint32_t revision; } playerdata_t; diff --git a/source/duke3d/src/premap.cpp b/source/duke3d/src/premap.cpp index 15cb343c1..601a67e01 100644 --- a/source/duke3d/src/premap.cpp +++ b/source/duke3d/src/premap.cpp @@ -35,7 +35,9 @@ static uint8_t precachehightile[2][MAXTILES>>3]; static int32_t g_precacheCount; -static inline void flag_precache(int tile, int type) +static int32_t NET_75_CHECK = 0; + +static void flag_precache(int32_t tile, int32_t type) { if (!(gotpic[tile>>3] & pow2char[tile&7])) g_precacheCount++; @@ -1906,8 +1908,10 @@ int G_EnterLevel(int gameMode) int16_t playerAngle; char levelName[BMAX_PATH]; + NET_75_CHECK++; // a major problem with how STAT_NETALLOC works, is that loadboard loads sprites directly into the arrays and does not take from + // STAT_NETALLOC, even though the loaded sprites are very, very likely to be relevant to the netcode. - if (!VOLUMEONE && Menu_HaveUserMap()) + if (!VOLUMEONE && G_HaveUserMap()) { if (engineLoadBoard(boardfilename, 0, &p0.pos, &playerAngle, &p0.cursectnum) < 0) { @@ -1934,6 +1938,12 @@ int G_EnterLevel(int gameMode) Bmemset(gotpic, 0, sizeof(gotpic)); Bmemset(precachehightile, 0, sizeof(precachehightile)); + NET_75_CHECK++; // resetpspritevars attempts to insert player 0's sprite, which isn't going to work because we don't have + // the STAT_NETALLOC sprites allocated yet. + + Net_NotifyNewGame(); + + prelevel(gameMode); G_AlignWarpElevators(); @@ -1978,7 +1988,7 @@ int G_EnterLevel(int gameMode) P_DoQuote(QUOTE_F1HELP,g_player[myconnectindex].ps); #endif - Net_NotifyNewGame(); + //Net_NotifyNewGame(); Net_ResetPrediction(); //g_player[myconnectindex].ps->palette = palette; @@ -2018,6 +2028,12 @@ int G_EnterLevel(int gameMode) G_DrawBackground(); G_DrawRooms(myconnectindex,65536); + if (g_netClient || g_netServer) // [75] : Initialize map states after map load + { + Net_InitMapStateHistory(); + Net_AddWorldToInitialSnapshot(); + } + Net_WaitForServer(); return 0; }