raze/source/duke3d/src/network.cpp

5251 lines
158 KiB
C++
Raw Normal View History

//-------------------------------------------------------------------------
/*
Copyright (C) 2017 EDuke32 developers and contributors
This file is part of EDuke32.
EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//-------------------------------------------------------------------------
/// Preprocessor directives:
///
/// ITERATE_ON_UPDATE: Every time the netcode updates the sprite linked lists, attempt to iterate through all of them.
/// SPRLIST_PRINT: Print in the console every time a sprite's status in the link list changes due to <see cref="Net_UpdateSpriteLinkedLists">
/// PACKET_RECV_PRINT: Print all received packets
/// NETCODE_DISABLE: Remove pretty much all of the game's interaction with the code in this file.
#include "duke3d.h"
#include "game.h"
#include "gamedef.h"
#include "network.h"
#include "premap.h"
#include "savegame.h"
Massive menu input control revamp/cleanup/factor. (added: input.[ch]) New Wii control defaults for the Wii Remote + Nunchuk and the Classic Controller. This includes new code added just so that the Home key brings up the menu in-game, reducing the need for a USB keyboard. On the technical side, raw joystick access (comparable to what is available for keyboard and mouse) is now present in jmact, on the game side. (added: joystick.[ch]) Using this new raw joystick access, I replaced tueidj's hack to map A and B to LMB/RMB and D-Pad Up/Down to the scrollwheel. I made the menus more friendly to mouse and joystick browsing by adding and unifying checks and clears for various buttons and gamefuncs. In fact, the majority of the time spent on this commit was tracking down problems that appeared with the factoring and trying to understand the menu system and the way input checks are precariously executed. In addition, "Press any key or button to continue" now truly means what it says. As a result of incorporating proper raw access into control.c instead of it directly accessing the implementaiton, the program *may* no longer be affected by joystick input when it is out of focus. This follows the pattern set by the mouse, and I think this is a positive change. A small bonus: In the classic/old keyboard preset, the key for Show_Console has been changed from '`' to 'C' because '`' is taken by Quick_Kick. git-svn-id: https://svn.eduke32.com/eduke32@2728 1a8010ca-5511-0410-912e-c29ae57300e0
2012-06-03 16:11:22 +00:00
#include "input.h"
#include "enet/enet.h"
#include "lz4.h"
#include "crc32.h"
#include "vfs.h"
// Data needed even if netcode is disabled
ENetHost *g_netServer = NULL;
ENetHost *g_netClient = NULL;
ENetPeer *g_netClientPeer = NULL;
enet_uint16 g_netPort = 23513;
int32_t g_netDisconnect = 0;
char g_netPassword[32];
int32_t g_networkMode = NET_CLIENT;
// to support (gcc only) -f-strict-aliasing, the netcode needs to specify that its 32 bit chunks to and from the
// packet code should not be subject to strict aliasing optimizations
// it would appear that only GCC does this, Clang doesn't seem to and Microsoft Visual C++ definitely doesn't.
#ifdef __GNUC__
#define TYPE_PUNNED __attribute__((__may_alias__))
#else
#define TYPE_PUNNED
#endif
typedef TYPE_PUNNED int32_t NetChunk32;
// Unfortunately faketimerhandler needs extra "help" because the Build Engine source doesn't include network.h.
#ifdef NETCODE_DISABLE
void faketimerhandler(void)
{
;
}
#else
void faketimerhandler(void)
{
if (g_netServer == NULL && g_netClient == NULL)
return;
enet_host_service(g_netServer ? g_netServer : g_netClient, NULL, 0);
}
static void Net_Disconnect(void);
static void Net_HandleClientPackets(void);
static void Net_HandleServerPackets(void);
#endif
void Net_GetPackets(void)
{
timerUpdate();
MUSIC_Update();
S_Cleanup();
G_HandleSpecialKeys();
#ifndef NETCODE_DISABLE
if (g_netDisconnect)
{
Net_Disconnect();
g_netDisconnect = 0;
if (g_gameQuit)
G_GameExit(" ");
return;
}
if (g_netServer)
{
Net_HandleClientPackets();
}
else if (g_netClient)
{
Net_HandleServerPackets();
}
#endif
}
// (the rest of the file)
#ifndef NETCODE_DISABLE
// this attribute is for variables that are only used to highlight interesting conditions in the code
// when I'm in a debugger.
#define NET_DEBUG_VAR EDUKE32_UNUSED
// internal data
#define SNAPSHOTS_TO_SAVE 32
#define MAX_SNAPSHOT_ACTORS 256
#define MAX_SNAPSHOT_WALLS MAXWALLS // 16384
#define MAX_SNAPSHOT_SECTORS MAXSECTORS // 4096
////////////////////////////////////////////////////////////////////////////////
// Server Update Packets
#pragma pack(push, 1)
typedef struct serverupdate_s
{
uint8_t header;
uint8_t numplayers;
input_t nsyn;
int32_t seed;
int16_t pause_on;
} serverupdate_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct serverplayerupdate_s
{
uint8_t extra;
int16_t cstat;
uint16_t owner;
uint16_t picnum;
uint16_t gotweapon;
uint8_t kickback_pic;
uint8_t frags[MAXPLAYERS];
int16_t inv_amount[GET_MAX];
int16_t ammo_amount[MAX_WEAPONS];
uint8_t curr_weapon;
uint8_t last_weapon;
uint8_t wantweaponfire;
uint8_t weapon_pos;
uint8_t frag_ps;
uint8_t frag;
uint8_t fraggedself;
uint8_t last_extra;
uint8_t pal;
uint16_t ping;
uint16_t newowner;
playerupdate_t player;
} serverplayerupdate_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct clientupdate_s
{
uint8_t header;
int32_t RevisionNumber;
input_t nsyn;
playerupdate_t player;
} clientupdate_t;
#pragma pack(pop)
// Client player ready for world updates (player spawned and ready)
static int ClientPlayerReady = 0;
// all zero NetActor
static const netactor_t cNullNetActor = netactor_t();
// all zero NetWall
static const netWall_t cNullNetWall = netWall_t();
// all zero NetSector
static const netSector_t cNullNetSector = netSector_t();
// the revision number reserved for the state of the map when it was loaded from the .map file.
// NOTE: Don't confuse this with a revision index. Index 0 into the snapshot history is NOT the initial map state.
// g_MapStateHistory[0] could be revision 0, revision 64, revision 128, etc...
static const uint32_t cInitialMapStateRevisionNumber = 0;
// the revision array starts here instead of zero (may as well have the initial map state be revision 0)...
static const uint32_t cStartingRevisionIndex = 1;
static const int32_t cLocSprite_DeletedSpriteStat = MAXSTATUS;
#ifdef CURRENTLY_UNUSED
static const int32_t cNetSprite_DeletedSpriteStat = STAT_NETALLOC;
#endif
//[75] When a client attempts to allocate a sprite during the game loop, which is not defined as a clientside sprite,
// the insert will go through, and the sprite will be put on this list.
//
// every time the client gets a new snapshot, this entire list will be deleted
static int32_t headscratchpadsprite = -1;
static int32_t nextscratchpadsprite[MAXSPRITES];
static char recbuf[180];
static int32_t g_chatPlayer = -1;
// note that NETINDEX_BITS must be large enough to store the largest of
// MAXWALLS, MAXSECTORS, and MAXSPRITES, plus one bit so that the stop
// code won't be mistaken for a valid index
//
// as of 5/4/2016 MAXSPRITES is 16384, which technically only needs 14 bits but I'm going to
// set this to 16 + 1 to make it a more even number
//
// also NETINDEX_BITS should not exceed 32
#define NETINDEX_BITS (16 + 1)
// worst case: 2 bits per field per stuct if every field in every struct is changed
#define WORLD_CHANGEBITSSIZE \
(MAXWALLS * ARRAY_SIZE(WallFields) * 2) + (MAXSECTORS * ARRAY_SIZE(SectorFields) * 2) + (MAXSPRITES * ARRAY_SIZE(ActorFields) * 2)
// each changed entry has a netindex + stop codes bits to indicate whether a field
// changed/zeroed convert to bytes....
#define WORLD_OVERHEADSIZE (((MAXSECTORS + MAXWALLS + MAXSPRITES + 3) * NETINDEX_BITS + WORLD_CHANGEBITSSIZE) >> 8) + 1
typedef struct netField_s
{
const char *name; // field name
int32_t offset; // offset from the start of the entity struct
int32_t bits; // field size
} netField_t;
#define SECTF(x) #x,(int32_t)(size_t)&((netSector_t*)0)->x
static netField_t SectorFields[] =
{
{ SECTF(wallptr), 16 },
{ SECTF(wallnum), 16 },
{ SECTF(ceilingz), 32 },
{ SECTF(floorz), 32 },
{ SECTF(ceilingstat), 16 },
{ SECTF(floorstat), 16 },
{ SECTF(ceilingpicnum), 16 },
{ SECTF(ceilingheinum), 16 },
{ SECTF(ceilingshade), 8 },
{ SECTF(ceilingpal), 8 },
{ SECTF(ceilingxpanning), 8 },
{ SECTF(ceilingypanning), 8 },
{ SECTF(floorpicnum), 16 },
{ SECTF(floorheinum), 16 },
{ SECTF(floorshade), 8 },
{ SECTF(floorpal), 8 },
{ SECTF(floorxpanning), 8 },
{ SECTF(floorypanning), 8 },
{ SECTF(visibility), 8 },
{ SECTF(fogpal), 8 },
{ SECTF(lotag), 16 },
{ SECTF(hitag), 16 },
{ SECTF(extra), 16 },
};
#undef SECTF
#define WALLF(x) #x,(int32_t)(size_t)&((netWall_t*)0)->x
static netField_t WallFields[] =
{
{ WALLF(x), 32 },
{ WALLF(y), 32 },
{ WALLF(point2), 16 },
{ WALLF(nextwall), 16 },
{ WALLF(nextsector), 16 },
{ WALLF(cstat), 16 },
{ WALLF(picnum), 16 },
{ WALLF(overpicnum), 16 },
{ WALLF(shade), 8 },
{ WALLF(pal), 8 },
{ WALLF(xrepeat), 8 },
{ WALLF(yrepeat), 8 },
{ WALLF(xpanning), 8 },
{ WALLF(ypanning), 8 },
{ WALLF(lotag), 16 },
{ WALLF(hitag), 16 },
{ WALLF(extra), 16 },
};
#undef WALLF
#define ACTF(x) #x,(int32_t)(size_t)&((netactor_t*)0)->x
static netField_t ActorFields[] =
{
{ ACTF(t_data_0), 32 },
{ ACTF(t_data_1), 32 },
{ ACTF(t_data_2), 32 },
{ ACTF(t_data_3), 32 },
{ ACTF(t_data_4), 32 },
{ ACTF(t_data_5), 32 },
{ ACTF(t_data_6), 32 },
{ ACTF(t_data_7), 32 },
{ ACTF(t_data_8), 32 },
{ ACTF(t_data_9), 32 },
#ifdef LUNATIC
// need to update this section if LUNATIC is ever brought back in
{ ACTF(hvel), 16 },
{ ACTF(vvel), 16 },
{ ACTF(startframe), 16 },
{ ACTF(numframes), 16 },
{ ACTF(viewtype), 16 },
{ ACTF(incval), 16 },
{ ACTF(delay), 16 },
#endif
{ ACTF(flags), 32 },
{ ACTF(bpos_x), 32 },
{ ACTF(bpos_y), 32 },
{ ACTF(bpos_z), 32 },
{ ACTF(floorz), 32 },
{ ACTF(ceilingz), 32 },
{ ACTF(lastvx), 32 },
{ ACTF(lastvy), 32 },
{ ACTF(lasttransport), 8},
{ ACTF(picnum), 16 },
{ ACTF(ang), 16 },
{ ACTF(extra), 16 },
{ ACTF(owner), 16 },
{ ACTF(movflag), 16 },
{ ACTF(tempang), 16 },
{ ACTF(timetosleep), 16 },
{ ACTF(stayput), 16 },
{ ACTF(dispicnum), 16 },
#if defined LUNATIC
{ ACTF(movflags), 16 },
#endif
{ ACTF(cgg), 8},
//------------------------------------------------------
// sprite fields
{ ACTF(spr_x), 32 },
{ ACTF(spr_y), 32 },
{ ACTF(spr_z), 32 },
{ ACTF(spr_cstat), 16 },
{ ACTF(spr_picnum), 16 },
{ ACTF(spr_shade), 8 },
{ ACTF(spr_pal), 8 },
{ ACTF(spr_clipdist), 8 },
{ ACTF(spr_blend), 8 },
{ ACTF(spr_xrepeat), 8 },
{ ACTF(spr_yrepeat), 8 },
{ ACTF(spr_xoffset), 8 },
{ ACTF(spr_yoffset), 8 },
{ ACTF(spr_sectnum), 16 },
{ ACTF(spr_statnum), 16 },
{ ACTF(spr_ang), 16 },
{ ACTF(spr_owner), 16 },
{ ACTF(spr_xvel), 16 },
{ ACTF(spr_yvel), 16 },
{ ACTF(spr_zvel), 16 },
{ ACTF(spr_lotag), 16 },
{ ACTF(spr_hitag), 16 },
{ ACTF(spr_extra), 16 },
//--------------------------------------------------------------
//spriteext fields
{ ACTF(ext_mdanimtims), 32 },
{ ACTF(ext_mdanimcur), 16 },
{ ACTF(ext_angoff), 16 },
{ ACTF(ext_pitch), 16 },
{ ACTF(ext_roll), 16 },
{ ACTF(ext_offset_x), 32 },
{ ACTF(ext_offset_y), 32 },
{ ACTF(ext_offset_z), 32 },
{ ACTF(ext_flags), 8 },
{ ACTF(ext_xpanning), 8 },
{ ACTF(ext_ypanning), 8 },
{ ACTF(ext_alpha), 0 }, // float
//--------------------------------------------------------------
//spritesmooth fields
{ ACTF(sm_smoothduration), 0 }, // float
{ ACTF(sm_mdcurframe), 16 },
{ ACTF(sm_mdoldframe), 16 },
{ ACTF(sm_mdsmooth), 16 },
};
#undef ACTF
// actual game struct data size
#define WORLD_DATASIZE (MAXSECTORS * sizeof(netSector_t) + MAXWALLS * sizeof(netWall_t) + MAXSPRITES * sizeof(netactor_t))
// max packet array size
#define MAX_WORLDBUFFER WORLD_DATASIZE + WORLD_OVERHEADSIZE
#ifdef CURRENTLY_UNUSED
// Just so you can get an idea of how much memory the netcode needs...
static const int64_t cWORLD_DataSize = WORLD_DATASIZE;
static const int64_t cWORLD_OverheadSize = WORLD_OVERHEADSIZE;
static const int64_t cWORLD_TotalSize = MAX_WORLDBUFFER;
// ...it's pretty big for now (!)
static const int64_t SnapshotArraySize = sizeof(netmapstate_t) * NET_REVISIONS;
#endif
// both the client and server store their current revision number here,
// when the client sends their revision to the server,
// the server stores it in g_player[that player's index].revision
static uint32_t g_netMapRevisionNumber = 0;
// In addition to the client keeping track of what revision the server is using
// (g_netRevisionNumber), it also increments its own revision that represents
// what version of the game the client has interpolated to.
static uint32_t g_cl_InterpolatedRevision = 0;
static netmapstate_t g_mapStartState;
static netmapstate_t g_cl_InterpolatedMapStateHistory[NET_REVISIONS];
// note that the map state number is not an index into here,
// to get the index into this array out of a map state number, do <Map state number> % 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 <any stat>, snap sprite MAXSTATUS -- delete delete
//
// this leaks a client sprite
// 3. game sprite MAXSTATUS, snap sprite STAT_NETALLOC or normal-- insert insert
// (implied in `else`)
//
// 4. game sprite STAT_NETALLOC, snap sprite Normal -- change insert
//
// 5. game sprite Normal, snap sprite STAT_NETALLOC -- change delete
//
// 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 = &sector[index];
netSector_t* snapshotSector = &snapshot->sector[index];
Net_CopySectorToNet(gameSector, snapshotSector, index);
}
Net_AddActorsToSnapshot(snapshot);
}
//------------------------------------------------------------------------------------------------------------------------
static void P_RemovePlayer(int32_t p)
{
// server obviously can't leave the game, and index 0 shows up for disconnect events from
// players that haven't gotten far enough into the connection process to get a player ID
if (p <= 0) return;
g_player[p].playerquitflag = 0;
Bsprintf(recbuf,"%s^00 is history!",g_player[p].user_name);
G_AddUserQuote(recbuf);
if (numplayers == 1)
S_PlaySound(GENERIC_AMBIENCE17);
if (g_player[myconnectindex].ps->gm & MODE_GAME)
{
if (screenpeek == p)
screenpeek = myconnectindex;
pub = NUMPAGES;
pus = NUMPAGES;
G_UpdateScreenArea();
P_QuickKill(g_player[p].ps);
if (voting == p)
{
for (p=0; p<MAXPLAYERS; p++)
{
g_player[p].vote = 0;
g_player[p].gotvote = 0;
}
voting = -1;
}
Bstrcpy(apStrings[QUOTE_RESERVED2],recbuf);
g_player[myconnectindex].ps->ftq = QUOTE_RESERVED2;
g_player[myconnectindex].ps->fta = 180;
}
}
static void Net_SendNewPlayer(int32_t newplayerindex)
{
packbuf[0] = PACKET_NUM_PLAYERS;
packbuf[1] = numplayers;
packbuf[2] = g_mostConcurrentPlayers;
packbuf[3] = ud.multimode;
packbuf[4] = newplayerindex;
packbuf[5] = g_networkMode;
packbuf[6] = myconnectindex;
enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 7, ENET_PACKET_FLAG_RELIABLE));
Dbg_PacketSent(PACKET_NUM_PLAYERS);
}
static void Net_SendPlayerIndex(int32_t index, ENetPeer *peer)
{
packbuf[0] = PACKET_PLAYER_INDEX;
packbuf[1] = index;
packbuf[2] = myconnectindex;
enet_peer_send(peer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 3, ENET_PACKET_FLAG_RELIABLE));
Dbg_PacketSent(PACKET_PLAYER_INDEX);
}
// sync a connecting player up with the current game state
static void Net_SyncPlayer(ENetEvent *event)
{
int32_t newPlayerIndex, j;
if (numplayers + g_netPlayersWaiting >= MAXPLAYERS)
{
enet_peer_disconnect_later(event->peer, DISC_SERVER_FULL);
initprintf("Refused peer; server full.\n");
return;
}
g_netPlayersWaiting++;
S_PlaySound(DUKE_GETWEAPON2);
// open a new slot if necessary and save off the resulting slot # for future reference
for (TRAVERSE_CONNECT(newPlayerIndex))
{
if (g_player[newPlayerIndex].playerquitflag == 0)
{
break;
}
}
if (newPlayerIndex == -1)
{
newPlayerIndex = g_mostConcurrentPlayers++;
}
NET_75_CHECK++; // is it necessary to se event->peer->data to the new player index in Net_SyncPlayer?
event->peer->data = (void *)(intptr_t)newPlayerIndex;
g_player[newPlayerIndex].netsynctime = totalclock;
g_player[newPlayerIndex].playerquitflag = 1;
NET_75_CHECK++; // Need to think of something better when finding a remaining slot for players.
for (j = 0; j < g_mostConcurrentPlayers - 1; j++)
{
connectpoint2[j] = j + 1;
}
connectpoint2[g_mostConcurrentPlayers - 1] = -1;
G_MaybeAllocPlayer(newPlayerIndex);
g_netPlayersWaiting--;
++numplayers;
++ud.multimode;
Net_SendNewPlayer(newPlayerIndex);
Net_SendPlayerIndex(newPlayerIndex, event->peer);
Net_SendClientInfo();
Net_SendUserMapName();
Net_SendNewGame(0, event->peer); // newly connecting player (Net_SyncPlayer)
}
static void display_betascreen(void)
{
rotatesprite_fs(160 << 16, 100 << 16, 65536, 0, BETASCREEN, 0, 0, 2 + 8 + 64 + BGSTRETCH);
rotatesprite_fs(160 << 16, (104) << 16, 60 << 10, 0, DUKENUKEM, 0, 0, 2 + 8);
rotatesprite_fs(160 << 16, (129) << 16, 30 << 11, 0, THREEDEE, 0, 0, 2 + 8);
if (PLUTOPAK) // JBF 20030804
rotatesprite_fs(160 << 16, (151) << 16, 30 << 11, 0, PLUTOPAKSPRITE + 1, 0, 0, 2 + 8);
}
static void Net_Disconnect(void)
{
if (g_netClient)
{
ENetEvent event;
if (g_netClientPeer)
enet_peer_disconnect_later(g_netClientPeer, 0);
while (enet_host_service(g_netClient, &event, 3000) > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
case ENET_EVENT_TYPE_NONE:
case ENET_EVENT_TYPE_RECEIVE:
if (event.packet)
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
numplayers = g_mostConcurrentPlayers = ud.multimode = 1;
myconnectindex = screenpeek = 0;
G_BackToMenu();
break;
}
}
enet_peer_reset(g_netClientPeer);
g_netClientPeer = NULL;
enet_host_destroy(g_netClient);
g_netClient = NULL;
return;
}
if (g_netServer)
{
int32_t peerIndex;
ENetEvent event;
for (peerIndex = 0; peerIndex < (signed)g_netServer->peerCount; peerIndex++)
{
enet_peer_disconnect_later(&g_netServer->peers[peerIndex], DISC_SERVER_QUIT);
}
while (enet_host_service(g_netServer, &event, 3000) > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
case ENET_EVENT_TYPE_NONE:
case ENET_EVENT_TYPE_RECEIVE:
case ENET_EVENT_TYPE_DISCONNECT:
if (event.packet)
{
enet_packet_destroy(event.packet);
}
break;
}
}
enet_host_destroy(g_netServer);
g_netServer = NULL;
}
}
// Only clients run this
static void Net_ReceiveDisconnect(ENetEvent *event)
{
g_netDisconnect = 1;
numplayers = g_mostConcurrentPlayers = ud.multimode = 1;
myconnectindex = screenpeek = 0;
G_BackToMenu();
switch (event->data)
{
case DISC_BAD_PASSWORD:
initprintf("Bad password.\n");
return;
case DISC_VERSION_MISMATCH:
initprintf("Version mismatch.\n");
return;
case DISC_INVALID:
initprintf("Invalid data detected.\n");
return;
case DISC_SERVER_QUIT:
initprintf("The server is quitting.\n");
return;
case DISC_SERVER_FULL:
initprintf("The server is full.\n");
return;
case DISC_KICKED:
initprintf("You have been kicked from the server.\n");
return;
case DISC_BANNED:
initprintf("You are banned from this server.\n");
return;
default:
initprintf("Disconnected.\n");
return;
}
}
static void Net_SendAcknowledge(ENetPeer *client)
{
if (!g_netServer)
return;
tempnetbuf[0] = PACKET_ACK;
tempnetbuf[1] = myconnectindex;
enet_peer_send(client, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
Dbg_PacketSent(PACKET_ACK);
}
static void Net_ExtractPlayerUpdate(playerupdate_t *update, int32_t type)
{
const int32_t playerindex = update->playerindex;
if (playerindex != myconnectindex)
{
g_player[playerindex].ps->pos = update->pos;
g_player[playerindex].ps->opos = update->opos;
g_player[playerindex].ps->vel = update->vel;
g_player[playerindex].ps->q16ang = update->q16ang;
g_player[playerindex].ps->q16horiz = update->q16horiz;
g_player[playerindex].ps->q16horizoff = update->q16horizoff;
}
if (type == PACKET_MASTER_TO_SLAVE)
{
g_player[playerindex].ping = update->ping;
g_player[playerindex].ps->dead_flag = update->deadflag;
g_player[playerindex].playerquitflag = update->playerquitflag;
}
}
// Server only
static void Net_ReceiveClientUpdate(ENetEvent *event)
{
int32_t playeridx;
clientupdate_t update;
if (event->packet->dataLength != sizeof(clientupdate_t))
{
return;
}
Bmemcpy(&update, (char *)event->packet->data, sizeof(clientupdate_t));
playeridx = (int32_t)(intptr_t)event->peer->data;
if (playeridx < 0 || playeridx >= MAXPLAYERS)
{
return;
}
g_player[playeridx].revision = update.RevisionNumber;
inputfifo[0][playeridx] = update.nsyn;
Net_ExtractPlayerUpdate(&update.player, PACKET_SLAVE_TO_MASTER);
}
static void Net_Server_SetupPlayer(int playerindex)
{
int16_t playerspriteindex;
playerspriteindex = g_player[playerindex].ps->i;
Bmemcpy(g_player[playerindex].ps, g_player[0].ps, sizeof(DukePlayer_t));
g_player[playerindex].ps->i = playerspriteindex;
changespritestat(playerspriteindex, STAT_PLAYER);
g_player[playerindex].ps->last_extra = sprite[g_player[playerindex].ps->i].extra = g_player[playerindex].ps->max_player_health;
sprite[g_player[playerindex].ps->i].cstat = 1 + 256;
actor[g_player[playerindex].ps->i].t_data[2] = actor[g_player[playerindex].ps->i].t_data[3]
= actor[g_player[playerindex].ps->i].t_data[4] = 0;
P_ResetPlayer(playerindex);
Net_SpawnPlayer(playerindex);
}
static void Net_ReceiveChallenge(uint8_t *pbuf, int32_t packbufleng, ENetEvent *event)
{
const uint16_t byteVersion = B_UNBUF16(&pbuf[1]);
const uint16_t netVersion = B_UNBUF16(&pbuf[3]);
const uint32_t crc = B_UNBUF32(&pbuf[5]);
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
if (byteVersion != BYTEVERSION || netVersion != NETVERSION)
{
enet_peer_disconnect_later(event->peer, DISC_VERSION_MISMATCH);
initprintf("Bad client protocol: version %u.%u\n", byteVersion, netVersion);
return;
}
if (crc != Bcrc32((uint8_t *)g_netPassword, Bstrlen(g_netPassword), 0))
{
enet_peer_disconnect_later(event->peer, DISC_BAD_PASSWORD);
initprintf("Bad password from client.\n");
return;
}
Net_SyncPlayer(event);
}
static void Net_ReceiveMessage(uint8_t *pbuf, int32_t packbufleng)
{
Bstrncpy(recbuf, (char *)pbuf + 2, packbufleng - 2);
recbuf[packbufleng - 2] = 0;
G_AddUserQuote(recbuf);
S_PlaySound(EXITMENUSOUND);
pus = pub = NUMPAGES;
}
static void Net_CheckForEnoughVotes()
{
// Only the server can decide map changes
if (!g_netServer || numplayers <= 1)
{
return;
}
int32_t requiredvotes;
// If there are just two players, both of them deserve a vote
if (numplayers == 2)
{
requiredvotes = 2;
}
else
{
// If more than two players, we need at least 50% of the players to vote
// Which means that if there's an odd number of players, we'll need slightly more than 50% of the vote.
requiredvotes = numplayers / 2;
if (numplayers % 2 == 1)
{
requiredvotes++;
}
}
int32_t numfor = 0;
int32_t numagainst = 0;
for (int32_t playerIndex = 0; playerIndex < MAXPLAYERS; playerIndex++)
{
if (g_player[playerIndex].gotvote)
{
if (g_player[playerIndex].vote)
{
numfor++;
}
else
{
numagainst++;
}
}
}
if (numfor >= requiredvotes)
{
Net_StartNewGame();
Net_SendNewGame(1, NULL); // map vote
}
else if (numagainst >= requiredvotes || (numfor + numagainst) == numplayers)
{
Net_SendMapVoteCancel(1);
}
}
static void Net_ReceiveMapVote(uint8_t *pbuf)
{
if (voting == myconnectindex && g_player[(uint8_t)pbuf[1]].gotvote == 0)
{
Bsprintf(tempbuf, "Confirmed vote from %s", g_player[(uint8_t)pbuf[1]].user_name);
G_AddUserQuote(tempbuf);
}
if (!g_netServer)
{
return;
}
g_player[(uint8_t)pbuf[1]].gotvote = 1;
g_player[(uint8_t)pbuf[1]].vote = pbuf[2];
Net_CheckForEnoughVotes();
}
static void Net_ReceiveMapVoteCancel(uint8_t *pbuf)
{
// Ignore if we're not voting
if (voting == -1)
{
return;
}
// Ignore cancellations from clients that did not initiate the map vote
if (voting != pbuf[1] && voting != myconnectindex)
{
return;
}
if (voting == myconnectindex || voting != pbuf[1])
{
Bsprintf(tempbuf, "Vote Failed");
}
else if (voting == pbuf[1])
{
Bsprintf(tempbuf, "%s^00 has canceled the vote", g_player[voting].user_name);
}
G_AddUserQuote(tempbuf);
if (g_netServer)
{
Net_SendMapVoteCancel(0);
}
voting = -1;
}
static void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver)
{
uint32_t byteIndex, j;
int32_t other = pbuf[packbufleng];
for (byteIndex = 1; pbuf[byteIndex]; byteIndex++)
{
g_player[other].user_name[byteIndex - 1] = pbuf[byteIndex];
}
g_player[other].user_name[byteIndex - 1] = 0;
byteIndex++;
g_player[other].ps->aim_mode = pbuf[byteIndex++];
g_player[other].ps->auto_aim = pbuf[byteIndex++];
g_player[other].ps->weaponswitch = pbuf[byteIndex++];
g_player[other].ps->palookup = g_player[other].pcolor = pbuf[byteIndex++];
g_player[other].pteam = pbuf[byteIndex++];
for (j = byteIndex; byteIndex - j < 10; byteIndex++)
{
g_player[other].wchoice[byteIndex - j] = pbuf[byteIndex];
}
if (fromserver)
{
g_player[other].playerquitflag = 1;
}
}
static void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng)
{
NET_75_CHECK++; // possible buffer bug here with a malicious client? if the file name didn't end in '\0', a hacker could "forget" to put '\0'
// at the end.
Bstrcpy(boardfilename, (char *)pbuf + 1);
boardfilename[packbufleng - 1] = 0;
Bcorrectfilename(boardfilename, 0);
if (boardfilename[0] != 0)
{
buildvfs_kfd i;
if ((i = kopen4loadfrommod(boardfilename, 0)) == buildvfs_kfd_invalid)
{
Bmemset(boardfilename, 0, sizeof(boardfilename));
Net_SendUserMapName();
}
else
{
kclose(i);
}
}
if (ud.m_level_number == 7 && ud.m_volume_number == 0 && boardfilename[0] == 0)
ud.m_level_number = 0;
}
static void Net_ExtractNewGame(newgame_t *newgame, int32_t menuonly)
{
ud.m_level_number = newgame->level_number;
ud.m_volume_number = newgame->volume_number;
ud.m_player_skill = newgame->player_skill;
ud.m_monsters_off = newgame->monsters_off;
ud.m_respawn_monsters = newgame->respawn_monsters;
ud.m_respawn_items = newgame->respawn_items;
ud.m_respawn_inventory = newgame->respawn_inventory;
ud.m_ffire = newgame->ffire;
ud.m_noexits = newgame->noexits;
ud.m_coop = newgame->coop;
if (!menuonly)
{
ud.level_number = newgame->level_number;
ud.volume_number = newgame->volume_number;
ud.player_skill = newgame->player_skill;
ud.monsters_off = newgame->monsters_off;
ud.respawn_monsters = newgame->respawn_monsters;
ud.respawn_monsters = newgame->respawn_items;
ud.respawn_inventory = newgame->respawn_inventory;
ud.ffire = newgame->ffire;
ud.noexits = newgame->noexits;
ud.coop = newgame->coop;
}
}
static void Net_ReceiveMapVoteInitiate(uint8_t *pbuf)
{
int32_t playerIndex;
Bmemcpy(&pendingnewgame, pbuf, sizeof(newgame_t));
Net_ExtractNewGame(&pendingnewgame, 1);
voting = pendingnewgame.connection;
vote_episode = pendingnewgame.volume_number;
vote_map = pendingnewgame.level_number;
Bsprintf(tempbuf, "%s^00 has called a vote to change map to %s (E%dL%d)", g_player[voting].user_name,
g_mapInfo[(uint8_t)(vote_episode * MAXLEVELS + vote_map)].name, vote_episode + 1, vote_map + 1);
G_AddUserQuote(tempbuf);
Bsprintf(tempbuf, "Press F1 to Accept, F2 to Decline");
G_AddUserQuote(tempbuf);
for (playerIndex = MAXPLAYERS - 1; playerIndex >= 0; playerIndex--)
{
g_player[playerIndex].vote = 0;
g_player[playerIndex].gotvote = 0;
}
g_player[voting].gotvote = g_player[voting].vote = 1;
}
static void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpacketp)
{
switch (pbuf[0])
{
case PACKET_MESSAGE:
Net_ReceiveMessage(pbuf, packbufleng);
break;
case PACKET_CLIENT_INFO:
Net_ReceiveClientInfo(pbuf, packbufleng, serverpacketp);
break;
case PACKET_RTS:
G_StartRTS(pbuf[1], 0);
break;
case PACKET_USER_MAP:
Net_ReceiveUserMapName(pbuf, packbufleng);
break;
case PACKET_MAP_VOTE:
Net_ReceiveMapVote(pbuf);
break;
case PACKET_MAP_VOTE_INITIATE: // call map vote
Net_ReceiveMapVoteInitiate(pbuf);
break;
case PACKET_MAP_VOTE_CANCEL: // cancel map vote
Net_ReceiveMapVoteCancel(pbuf);
break;
}
}
static void Net_ParseClientPacket(ENetEvent *event)
{
uint8_t *pbuf = event->packet->data;
int32_t packbufleng = event->packet->dataLength;
int32_t other = pbuf[--packbufleng];
NET_DEBUG_VAR enum DukePacket_t packetType = (enum DukePacket_t)pbuf[0];
#ifdef PACKET_RECV_PRINT
initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng);
#endif
switch (pbuf[0])
{
case PACKET_SLAVE_TO_MASTER: //[1] (receive slave sync buffer)
Net_ReceiveClientUpdate(event);
break;
case PACKET_PLAYER_READY:
{
if ((other == 0) || (other == myconnectindex) || (other >= MAXPLAYERS))
{
break;
}
// At intermission, just set the ready flag and wait for the other players.
if (g_player[myconnectindex].ps->gm & MODE_EOL)
{
g_player[other].ready = 1;
}
else
{
// otherwise if the server's in a map now, set up the player immediately.
Net_Server_SetupPlayer(other);
}
break;
}
case PACKET_PLAYER_PING:
if (g_player[myconnectindex].ps->gm & MODE_GAME)
{
packbuf[0] = PACKET_PLAYER_PING;
packbuf[1] = myconnectindex;
enet_peer_send(event->peer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
Dbg_PacketSent(PACKET_PLAYER_PING);
}
g_player[other].pingcnt++;
break;
case PACKET_AUTH:
Net_ReceiveChallenge(pbuf, packbufleng, event);
break;
default:
Net_ParsePacketCommon(pbuf, packbufleng, 0);
break;
}
}
static void Net_HandleClientPackets(void)
{
ENetEvent event;
// pull events from the wire into the packet queue without dispatching them, once per Net_GetPackets() call
enet_host_service(g_netServer, NULL, 0);
// dispatch any pending events from the local packet queue
while (enet_host_check_events(g_netServer, &event) > 0)
{
const intptr_t playeridx = (intptr_t)event.peer->data;
if (playeridx < 0 || playeridx >= MAXPLAYERS)
{
enet_peer_disconnect_later(event.peer, DISC_INVALID);
buildprint("Invalid player id (", playeridx, ") from client.\n");
continue;
}
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
{
char ipaddr[32];
enet_address_get_host_ip(&event.peer->address, ipaddr, sizeof(ipaddr));
OSD_Printf("A new client connected from %s:%u.\n", ipaddr, event.peer->address.port);
//[75] Temporary: For now the netcode can't handle more players connecting than there are player starts.
if (g_playerSpawnCnt <= ud.multimode)
{
OSD_Printf("Connection dropped: No player spawn point available for this new player.");
break;
}
Net_SendAcknowledge(event.peer);
break;
}
case ENET_EVENT_TYPE_RECEIVE:
Net_ParseClientPacket(&event);
// broadcast takes care of enet_packet_destroy itself
// we set the state to disconnected so enet_host_broadcast
// doesn't send the player back his own packets
if ((event.channelID == CHAN_GAMESTATE && event.packet->data[0] > PACKET_BROADCAST)
|| event.channelID == CHAN_CHAT)
{
const ENetPacket *pak = event.packet;
event.peer->state = ENET_PEER_STATE_DISCONNECTED;
enet_host_broadcast(g_netServer, event.channelID,
enet_packet_create(pak->data, pak->dataLength, pak->flags&ENET_PACKET_FLAG_RELIABLE));
event.peer->state = ENET_PEER_STATE_CONNECTED;
}
enet_packet_destroy(event.packet);
g_player[playeridx].ping = (event.peer->lastRoundTripTime + event.peer->roundTripTime) / 2;
break;
case ENET_EVENT_TYPE_DISCONNECT:
numplayers--;
ud.multimode--;
P_RemovePlayer(playeridx);
g_player[playeridx].revision = cInitialMapStateRevisionNumber;
packbuf[0] = PACKET_PLAYER_DISCONNECTED;
packbuf[1] = playeridx;
packbuf[2] = numplayers;
packbuf[3] = ud.multimode;
packbuf[4] = g_mostConcurrentPlayers;
packbuf[5] = myconnectindex;
enet_host_broadcast(g_netServer, CHAN_GAMESTATE,
enet_packet_create(&packbuf[0], 6, ENET_PACKET_FLAG_RELIABLE));
initprintf("%s disconnected.\n", g_player[playeridx].user_name);
event.peer->data = NULL;
Dbg_PacketSent(PACKET_PLAYER_DISCONNECTED);
break;
default:
break;
}
}
}
static void Net_ReceiveServerUpdate(ENetEvent *event)
{
serverupdate_t serverupdate;
serverplayerupdate_t playerupdate;
if (((event->packet->dataLength - sizeof(serverupdate_t)) % sizeof(serverplayerupdate_t)) != 0)
{
return;
}
uint8_t *updatebuf = (uint8_t *)event->packet->data;
Bmemcpy(&serverupdate, updatebuf, sizeof(serverupdate_t));
updatebuf += sizeof(serverupdate_t);
inputfifo[0][0] = serverupdate.nsyn;
ud.pause_on = serverupdate.pause_on;
ticrandomseed = serverupdate.seed;
for (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;
}
}
// sends the version and a simple crc32 of the current password, all verified by the server before the connection can continue
static void Net_SendChallenge()
{
if (!g_netClientPeer)
{
return;
}
tempnetbuf[0] = PACKET_AUTH;
B_BUF16(&tempnetbuf[1], BYTEVERSION);
B_BUF16(&tempnetbuf[3], NETVERSION);
B_BUF32(&tempnetbuf[5], Bcrc32((uint8_t *)g_netPassword, Bstrlen(g_netPassword), 0));
tempnetbuf[9] = myconnectindex;
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 10, ENET_PACKET_FLAG_RELIABLE));
Dbg_PacketSent(PACKET_AUTH);
}
static void Net_ReceiveAcknowledge(uint8_t *pbuf, int32_t packbufleng)
{
UNREFERENCED_PARAMETER(pbuf); // remove when this variable is used
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
Net_SendChallenge();
}
// client only
static void Net_ReceiveNewGame(ENetEvent *event)
{
ClientPlayerReady = 0;
if ((vote_map + vote_episode + voting) != -3)
G_AddUserQuote("Vote Succeeded");
Bmemcpy(&pendingnewgame, event->packet->data, sizeof(newgame_t));
Net_StartNewGame();
packbuf[0] = PACKET_PLAYER_READY;
packbuf[1] = myconnectindex;
if (g_netClientPeer)
{
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
Dbg_PacketSent(PACKET_PLAYER_READY);
if (g_netClient)
{
NET_75_CHECK++; // are these extra calls to create initial snapshots necessary or is it sufficient to just have it in
// G_EnterLevel?
/*
Net_InitMapStateHistory();
Net_AddWorldToInitialSnapshot();
*/
ClientPlayerReady = 1;
}
}
//[75] Note: DON'T set the initial map state or initialize the map state history in the packet code,
// The client didn't load the map until G_EnterLevel
g_player[myconnectindex].ps->gm = MODE_GAME;
ready2send = 1;
}
static void Net_ReceiveNewPlayer(uint8_t *pbuf, int32_t packbufleng)
{
int32_t i;
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
numplayers = pbuf[1];
g_mostConcurrentPlayers = pbuf[2];
ud.multimode = pbuf[3];
if (pbuf[4]) // ID of new player
{
g_player[pbuf[4]].playerquitflag = 1;
if (!g_player[pbuf[4]].ps)
{
g_player[pbuf[4]].ps = (DukePlayer_t *)Xcalloc(1, sizeof(DukePlayer_t));
}
if (!g_player[pbuf[4]].inputBits)
{
g_player[pbuf[4]].inputBits = (input_t *)Xcalloc(1, sizeof(input_t));
}
}
if (pbuf[5] == NET_DEDICATED_SERVER)
{
g_networkMode = NET_DEDICATED_CLIENT;
}
for (i = 0; i < g_mostConcurrentPlayers - 1; i++)
{
connectpoint2[i] = i + 1;
}
connectpoint2[g_mostConcurrentPlayers - 1] = -1;
S_PlaySound(DUKE_GETWEAPON2);
// myconnectindex is 0 until we get PACKET_PLAYER_INDEX
if (myconnectindex != 0)
{
Net_SendClientInfo();
}
}
static void Net_ReceivePlayerIndex(uint8_t *pbuf, int32_t packbufleng)
{
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
myconnectindex = pbuf[1];
g_player[myconnectindex].playerquitflag = 1;
Net_SendClientInfo();
}
static void Net_ParseServerPacket(ENetEvent *event)
{
uint8_t *pbuf = event->packet->data;
int32_t packbufleng = event->packet->dataLength;
// input_t *nsyn;
NET_DEBUG_VAR enum DukePacket_t packetType = (enum DukePacket_t) pbuf[0];
--packbufleng;
#ifdef PACKET_RECV_PRINT
initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng);
#endif
switch (pbuf[0])
{
case PACKET_MASTER_TO_SLAVE:
if (!(g_player[myconnectindex].ps->gm & MODE_GAME))
{
return;
}
Net_ReceiveServerUpdate(event);
break;
case PACKET_NEW_GAME:
Net_ReceiveNewGame(event);
break;
case PACKET_ACK:
Net_ReceiveAcknowledge(pbuf, packbufleng);
break;
case PACKET_NUM_PLAYERS:
Net_ReceiveNewPlayer(event->packet->data, event->packet->dataLength);
break;
case PACKET_PLAYER_INDEX:
Net_ReceivePlayerIndex(event->packet->data, event->packet->dataLength);
break;
case PACKET_PLAYER_DISCONNECTED:
if ((g_player[myconnectindex].ps->gm & MODE_GAME))
{
P_RemovePlayer(pbuf[1]);
}
numplayers = pbuf[2];
ud.multimode = pbuf[3];
g_mostConcurrentPlayers = pbuf[4];
break;
case PACKET_PLAYER_SPAWN:
if (!(g_player[myconnectindex].ps->gm & MODE_GAME))
{
break;
}
P_ResetPlayer(pbuf[1]);
Bmemcpy(&g_player[pbuf[1]].ps->pos.x, &pbuf[2], sizeof(vec3_t) * 2);
Bmemcpy(&sprite[g_player[pbuf[1]].ps->i], &pbuf[2], sizeof(vec3_t));
break;
case PACKET_PLAYER_PING:
g_player[0].pingcnt++;
return;
case PACKET_FRAG:
if (!(g_player[myconnectindex].ps->gm & MODE_GAME))
{
break;
}
g_player[pbuf[1]].ps->frag_ps = pbuf[2];
actor[g_player[pbuf[1]].ps->i].picnum = pbuf[3];
ticrandomseed = B_UNBUF32(&pbuf[4]);
P_FragPlayer(pbuf[1]);
break;
// [75]
case PACKET_WORLD_UPDATE:
Net_ReadWorldUpdate(pbuf, packbufleng);
break;
default:
Net_ParsePacketCommon(pbuf, packbufleng, 1);
break;
}
}
static void Net_HandleServerPackets(void)
{
ENetEvent event;
enet_host_service(g_netClient, NULL, 0);
while (enet_host_check_events(g_netClient, &event) > 0)
{
if (event.type == ENET_EVENT_TYPE_DISCONNECT)
{
Net_ReceiveDisconnect(&event);
}
else if (event.type == ENET_EVENT_TYPE_RECEIVE)
{
Net_ParseServerPacket(&event);
}
enet_packet_destroy(event.packet);
}
}
////////////////////////////////////////////////////////////////////////////////
// Map Update Packets
//Insert a sprite from STAT_NETALLOC
static int32_t Net_DoInsertSprite(int32_t sect, int32_t stat)
{
int32_t i = headspritestat[STAT_NETALLOC];
// This means that we've run out of server-side actors
if (i < 0)
{
Net_Error_Disconnect("Out of server side actors");
return i;
}
changespritestat(i, stat);
do_insertsprite_at_headofsect(i, sect);
return i;
}
////////////////////////////////////////////////////////////////////////////////
// Player Updates
static void Net_FillPlayerUpdate(playerupdate_t *update, int32_t player)
{
update->playerindex = player;
update->pos = g_player[player].ps->pos;
update->opos = g_player[player].ps->opos;
update->vel = g_player[player].ps->vel;
update->q16ang = g_player[player].ps->q16ang;
update->q16horiz = g_player[player].ps->q16horiz;
update->q16horizoff = g_player[player].ps->q16horizoff;
update->ping = g_player[player].ping;
update->deadflag = g_player[player].ps->dead_flag;
update->playerquitflag = g_player[player].playerquitflag;
}
////////////////////////////////////////////////////////////////////////////////
// New Game Packets
// set all actors, walls, and sectors in a snapshot to their Null states.
static void Net_InitMapState(netmapstate_t* mapState)
{
netactor_t sprDefault = cNullNetActor;
int32_t index = 0;
sprDefault.netIndex = cSTOP_PARSING_CODE;
mapState->maxActorIndex = 0;
// this code is just a (slow) memset as it is now,
// it may be a good idea to use "baselines", which can reduce the amount
// of delta encoding when a sprite is first added. This
// could be a good optimization to consider later.
for (index = 0; index < MAXSPRITES; index++)
{
mapState->actor[index] = sprDefault;
}
for (index = 0; index < MAXWALLS; index++)
{
mapState->wall[index] = cNullNetWall;
}
for (index = 0; index < MAXSECTORS; index++)
{
mapState->sector[index] = cNullNetSector;
}
// set the revision number to a valid but easy to identify number,
// this is just to make it easier to debug.
mapState->revisionNumber = 8675309;
}
// Both client and server execute this
static void Net_ResetPlayers()
{
int32_t playerIndex;
for (TRAVERSE_CONNECT(playerIndex))
{
P_ResetWeapons(playerIndex);
P_ResetInventory(playerIndex);
g_player[playerIndex].revision = cInitialMapStateRevisionNumber;
}
}
static void Net_ResetPlayerReady()
{
int32_t playerindex;
for (TRAVERSE_CONNECT(playerindex))
{
g_player[playerindex].ready = 0;
}
}
// Packet code
//---------------------------------------------------------------------------------------------------------------------------------
// enforce signed/unsigned and overflow behavior for WriteBits smaller than 32
// remember that all of the WriteX functions only take 32 bit integers as input...
#define PICKY_TYPECHECK 1
#define MAX_PACKET_STRING 1024
#define LOG_MESSAGE_BITS 1
#define NetNumberOfIndexes (1 << NETINDEX_BITS)
#define IndexesOK (MAXWALLS < NetNumberOfIndexes) && (MAXSPRITES < NetNumberOfIndexes) && (MAXSECTORS < NetNumberOfIndexes)
#if(!IndexesOK)
#error "network.cpp: game arrays are now too big to send over the network, please update NETINDEX_BITS to be the right length to store a wall, sprite, and sector index"
#endif
#define STRUCTINDEX_BITS 8 // hopefully no game structs ever have more than 255 fields....
#define DEBUG_CHANGEDINDEXES 1
#define FLOAT_INT_BITS 13
// remember that the minimum negative number is the sign bit + all zeros
const int32_t cTruncInt_Min = -(1 << (FLOAT_INT_BITS - 1));
const int32_t cTruncInt_Max = (1 << (FLOAT_INT_BITS - 1)) - 1;
typedef struct NetBuffer_s
{
uint8_t *Data;
int32_t Bit;
int32_t ReadCurByte;
int32_t CurSize;
int32_t MaxSize; // set in NetBuffer_Init to the size of the byte array that this struct referrs to in Data
} NetBuffer_t;
// NOTE: does NOT fill byteArray with zeros
static void NetBuffer_Init(NetBuffer_t *buffer, uint8_t *byteArray, int32_t arrayLength)
{
// fill all the entries of the netbuffer struct (reminder: the byte array is not part of the struct)
Bmemset(buffer, 0, sizeof(*buffer));
buffer->Data = byteArray;
buffer->MaxSize = arrayLength;
}
///<summary>
///guarantees that the destination string will be nul terminated
///</summary>
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); // {<NetIndex>} sprite index
NetBuffer_WriteBits(netBuffer, 1, 1); // {<NetIndex>, 1} sprite deleted
return;
}
// all fields should be 32 bits to avoid any compiler packing issues
// the "number" field is not part of the field list
//
// if this assert fails, check that all of the entries in net*_t are in the engine's type
Bassert(cFieldsInStruct + 1 == (sizeof(*from) / 4));
if (to->netIndex < 0 || to->netIndex >= cMaxStructs)
{
Net_Error_Disconnect("Netbuffer_WriteDeltaNetActor: Invalid To NetIndex");
return;
}
maxChgIndex = 0;
for (fieldIndex = 0, fieldPtr = ActorFields; fieldIndex < cFieldsInStruct; fieldIndex++, fieldPtr++)
{
fromField = (int32_t 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); // {<NetIndex>} sprite index
NetBuffer_WriteBits(netBuffer, 0, 1); // {<NetIndex>, 0} sprite NOT deleted
NetBuffer_WriteBits(netBuffer, 0, 1); // {<NetIndex>, 0,0} sprite has NOT changed.
return;
}
// if we got to this point, the sprite / actor exists and has changed
NetBuffer_WriteBits(netBuffer, to->netIndex, NETINDEX_BITS); // {<NetIndex>} sprite index
NetBuffer_WriteBits(netBuffer, 0, 1); // {<NetIndex>, 0} sprite/actor NOT deleted.
NetBuffer_WriteBits(netBuffer, 1, 1); // {<NetIndex>, 0,1} sprite/actor HAS changed.
//--------------------------------------------------
// then...
NetBuffer_WriteBits(netBuffer, maxChgIndex, STRUCTINDEX_BITS); // Write Max change index
// For each field in struct...
for (fieldIndex = 0, fieldPtr = ActorFields; fieldIndex < maxChgIndex; fieldIndex++, fieldPtr++)
{
const int8_t isFloatingPointField = (fieldPtr->bits == 0);
fromField = (int32_t 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, <special float behavior>}
}
else
{
if (*toField == 0)
{
NetBuffer_WriteBits(netBuffer, 0, 1); // {1, 0} Zero this field
}
else
{
NetBuffer_WriteBits(netBuffer, 1, 1); // {1, 1} don't zero this int field
NetBuffer_WriteBits(netBuffer, *toField, fieldPtr->bits); // {1, 1, <value>} new field value
}
}
}
}
static void Net_WriteNetActorsToBuffer(NetBuffer_t* netBuffer, const netmapstate_t* from, const netmapstate_t* to)
{
const netactor_t* fromActor = NULL;
const netactor_t* toActor = NULL;
int16_t actorIndex = 0;
int16_t fromMaxIndex = 0;
if (!from)
{
fromMaxIndex = 0;
}
else
{
fromMaxIndex = from->maxActorIndex;
}
while (
actorIndex < to->maxActorIndex
||
actorIndex < fromMaxIndex
)
{
// load actor pointers using actor indexes
if (actorIndex >= to->maxActorIndex)
{
toActor = NULL;
}
else
{
toActor = &to->actor[actorIndex];
if (toActor->netIndex == cSTOP_PARSING_CODE)
{
toActor = NULL;
}
}
if (actorIndex >= fromMaxIndex)
{
fromActor = NULL;
}
else
{
fromActor = &from->actor[actorIndex];
if (fromActor->netIndex == cSTOP_PARSING_CODE)
{
fromActor = NULL;
}
}
NetBuffer_WriteDeltaNetActor(netBuffer, fromActor, toActor, 0);
actorIndex++;
}
}
static void Net_WriteWorldToBuffer(NetBuffer_t* netBuffer, const netmapstate_t* fromSnapshot, const netmapstate_t* toSnapshot)
{
int32_t index = 0;
for (index = 0; index < numwalls; index++)
{
Bassert(index < MAXWALLS);
const netWall_t* fromWall = &fromSnapshot->wall[index];
const netWall_t* toWall = &toSnapshot->wall[index];
NetBuffer_WriteDeltaNetWall(netBuffer, fromWall, toWall);
}
NetBuffer_WriteBits(netBuffer, cSTOP_PARSING_CODE, NETINDEX_BITS);
for (index = 0; index < numsectors; index++)
{
Bassert(index < MAXSECTORS);
const netSector_t* fromSector = &fromSnapshot->sector[index];
const netSector_t* toSector = &toSnapshot->sector[index];
NetBuffer_WriteDeltaNetSector(netBuffer, fromSector, toSector);
}
NetBuffer_WriteBits(netBuffer, cSTOP_PARSING_CODE, NETINDEX_BITS);
Net_WriteNetActorsToBuffer(netBuffer, fromSnapshot, toSnapshot);
NetBuffer_WriteBits(netBuffer, cSTOP_PARSING_CODE, NETINDEX_BITS); // end of actors/sprites
}
// buffer -> net struct functions
//----------------------------------------------------------------------------------------------------------
// I agonized over whether to just make a more generic NetBuffer_ReadDeltaWorldEntry since there's so little
// difference between walls and sectors for this function but my attempts ended up being much harder
// to read and probably more error prone than using "clipboard" inheritance, I really hope I made the right call here...
static void NetBuffer_ReadDeltaWall(NetBuffer_t *netBuffer, const netWall_t *from, netWall_t *to, uint16_t netIndex)
{
int32_t fieldIndex,
maxChgIndex; // the number of fields that changed in this delta struct from the server
const int32_t cMaxStructs = MAXWALLS;
const int32_t cStructFields = ARRAY_SIZE(WallFields); // the number of fields in the full struct
netField_t *cFieldsArray = &(WallFields[0]);
netField_t *field;
const NetChunk32 *fromField;
NetChunk32 *toField;
if (netIndex >= cMaxStructs)
{
Net_Error_Disconnect("NetBuffer_ReadDeltaWall: Bad Netindex to read.");
return;
}
if (from->netIndex != netIndex)
{
Net_Error_Disconnect("Unexpected from struct netIndex. from struct netIndex should match the netIndex parameter.");
return;
}
maxChgIndex = NetBuffer_ReadBits(netBuffer, STRUCTINDEX_BITS);
if (maxChgIndex > cStructFields || maxChgIndex < 0)
{
Net_Error_Disconnect("NetBuffer_ReadDeltaWall: Invalid delta field count from client.");
return;
}
to->netIndex = netIndex;
// for each field of the delta struct
for (fieldIndex = 0, field = cFieldsArray; fieldIndex < maxChgIndex; fieldIndex++, field++)
{
fromField = (int32_t const *)((int8_t const *)from + field->offset);
toField = (int32_t *) ((int8_t *) to + field->offset);
// no change to this field
if (!NetBuffer_ReadBits(netBuffer, 1))
{
*toField = *fromField;
}
// field has changed
else
{
// zero the field
if (NetBuffer_ReadBits(netBuffer, 1) == 0)
{
*toField = 0;
}
// read the whole field
else
{
*toField = NetBuffer_ReadBits(netBuffer, field->bits);
}
}
}
// if the delta for this struct doesn't include every field of the struct (likely)
// just set every field after the end of this delta struct to the "from" value
for (fieldIndex = maxChgIndex, field = &cFieldsArray[maxChgIndex]; fieldIndex < cStructFields; fieldIndex++, field++)
{
fromField = (NetChunk32 const *)((int8_t const *)from + field->offset);
toField = (NetChunk32 *) ((int8_t *) to + field->offset);
// no change on all the rest of the fields of the struct
*toField = *fromField;
}
}
static void NetBuffer_ReadDeltaSector(NetBuffer_t *netBuffer, const netSector_t *from, netSector_t *to, uint16_t netIndex)
{
int32_t fieldIndex,
maxChgIndex; // the number of fields that changed in this delta struct from the server
const int32_t cMaxStructs = MAXSECTORS;
const int32_t cStructFields = ARRAY_SIZE(SectorFields); // the number of fields in the full struct
netField_t *cFieldsArray = &(SectorFields[0]);
netField_t *field;
const NetChunk32 *fromField;
NetChunk32 *toField;
if (netIndex >= cMaxStructs)
{
Net_Error_Disconnect("NetBuffer_ReadDeltaSector: Bad Netindex to read.");
return;
}
if (from->netIndex != netIndex)
{
Net_Error_Disconnect("Unexpected from struct netIndex. from struct netIndex should match the netIndex parameter.");
return;
}
maxChgIndex = NetBuffer_ReadBits(netBuffer, STRUCTINDEX_BITS);
if (maxChgIndex > cStructFields || maxChgIndex < 0)
{
Net_Error_Disconnect("NetBuffer_ReadDeltaSector: Invalid delta field count from client.");
return;
}
to->netIndex = netIndex;
// for each field of the delta struct
for (fieldIndex = 0, field = cFieldsArray; fieldIndex < maxChgIndex; fieldIndex++, field++)
{
fromField = (int32_t const *)((int8_t const *)from + field->offset);
toField = (int32_t *) ((int8_t *) to + field->offset);
// no change to this field
if (!NetBuffer_ReadBits(netBuffer, 1))
{
*toField = *fromField;
}
// field has changed
else
{
// zero the field
if (NetBuffer_ReadBits(netBuffer, 1) == 0)
{
*toField = 0;
}
// read the whole field
else
{
*toField = NetBuffer_ReadBits(netBuffer, field->bits);
}
}
}
// if the delta for this struct doesn't include every field of the struct (likely)
// just set every field after the end of this delta struct to the "from" value
for (fieldIndex = maxChgIndex, field = &cFieldsArray[maxChgIndex]; fieldIndex < cStructFields; fieldIndex++, field++)
{
// I am not sure if this is type punning or not, just to be safe
fromField = (NetChunk32 const *)((int8_t const *)from + field->offset);
toField = (NetChunk32 *) ((int8_t *) to + field->offset);
// no change on all the rest of the fields of the struct
*toField = *fromField;
}
}
static void NetBuffer_ReadDeltaActor(NetBuffer_t* netBuffer, const netactor_t *from, netactor_t *to, uint16_t actorIndex)
{
int32_t fieldIndex,
maxChgIndex; // the number of fields that changed in this delta struct from the server
const int32_t cMaxStructs = MAXWALLS;
const int32_t cStructFields = ARRAY_SIZE(ActorFields); // the number of fields in the full struct
netField_t *cFieldsArray = &(ActorFields[0]);
netField_t *field;
const NetChunk32 *fromField;
NetChunk32 *toField;
int32_t removeActor;
int32_t actorChanged; // this is only used if the packet is sent with "force"
int32_t fromActorDeleted = (from->spr_sectnum == MAXSECTORS);
int32_t actorIndexOK = ((fromActorDeleted) || (from->netIndex == actorIndex));
if (actorIndex >= cMaxStructs)
{
Net_Error_Disconnect("NetBuffer_ReadDeltaActor: Bad Netindex to read.");
}
if (!actorIndexOK)
{
Net_Error_Disconnect("Unexpected from struct netIndex. from struct netIndex should match the netIndex parameter.");
return;
}
removeActor = NetBuffer_ReadBits(netBuffer, 1); // read actor deleted bit
// if this actor is being deleted, fill it with zeros and set its netIndex to STOP_PARSING_CODE
if (removeActor == 1)
{
Net_InitNetActor(to);
return;
}
actorChanged = NetBuffer_ReadBits(netBuffer, 1); // read actor changed bit
to->netIndex = actorIndex;
if (actorChanged == 0)
{
*to = *from;
if (to->netIndex == cSTOP_PARSING_CODE)
{
Net_Error_Disconnect("Read invalid netindex from delta actor buffer.");
}
return;
}
maxChgIndex = NetBuffer_ReadBits(netBuffer, STRUCTINDEX_BITS); // max change index
if (maxChgIndex > cStructFields || maxChgIndex < 0)
{
Net_Error_Disconnect("NetBuffer_ReadDeltaActor: Invalid delta field count from server.");
}
// for each field of the delta struct
for (fieldIndex = 0, field = cFieldsArray; fieldIndex < maxChgIndex; fieldIndex++, field++)
{
int8_t isFloatField = (field->bits == 0);
fromField = (int32_t const *)((int8_t const *)from + field->offset);
toField = (int32_t *) ((int8_t *) to + field->offset);
// no change to this field
if (!NetBuffer_ReadBits(netBuffer, 1))
{
*toField = *fromField;
}
// field has changed
else
{
if (isFloatField)
{
int32_t notZeroed = NetBuffer_ReadBits(netBuffer, 1);
if (notZeroed == 0)
{
*(float *)toField = 0.0f;
}
else
{
int32_t sentAsRealFloat = NetBuffer_ReadBits(netBuffer, 1);
if (sentAsRealFloat == 0)
{
// float was written as a truncated FLOAT_INT_BITS integer
NetChunk32 truncated = NetBuffer_ReadBits(netBuffer, FLOAT_INT_BITS);
// remove bias to allow this to be signed
truncated -= cTruncInt_Max;
*(float *)toField = (float)truncated;
}
else
{
// read the raw float data directly from the buffer
*toField = NetBuffer_ReadBits(netBuffer, 32);
}
}
}
// not a float field
else
{
// zero the field
if (NetBuffer_ReadBits(netBuffer, 1) == 0)
{
*toField = 0;
}
// read the whole field
else
{
*toField = NetBuffer_ReadBits(netBuffer, field->bits);
}
}
}
}
// if the delta for this struct doesn't include every field of the struct (likely)
// just set every field after the end of this delta struct to the "from" value
for (fieldIndex = maxChgIndex, field = &cFieldsArray[maxChgIndex]; fieldIndex < cStructFields; fieldIndex++, field++)
{
// I am really not 100% sure if this is type punning or not, just to be safe.
fromField = (NetChunk32 const *)((int8_t const *)from + field->offset);
toField = (NetChunk32 *) ((int8_t *) to + field->offset);
// no change on all the rest of the fields of the struct
*toField = *fromField;
}
if (to->netIndex == cSTOP_PARSING_CODE)
{
Net_Error_Disconnect("Read invalid netindex from delta actor buffer.");
}
}
// Using oldSnapshot as the "From" snapshot, parse the data in netBuffer as a diff from
// oldSnapshot to make newSnapshot.
static void Net_ParseWalls(NetBuffer_t *netBuffer, netmapstate_t *oldSnapshot, netmapstate_t *newSnapshot)
{
int32_t newSnapshotNetIndex = 0;
int32_t oldSnapshotNetIndex = 0;
netWall_t *oldSnapshotStruct = NULL;
netWall_t *newSnapshotStruct = NULL;
const int32_t cMaxStructIndex = numwalls - 1;
NET_DEBUG_VAR int32_t DEBUG_NumberOfStructsInPacket = 0;
NET_DEBUG_VAR int32_t DEBUG_FirstChangedIndex = -1234;
NET_DEBUG_VAR int32_t DEBUG_LastChangedIndex = -1234;
#if DEBUG_CHANGEDINDEXES
NET_DEBUG_VAR int32_t DEBUG_ChangedIndexes[NetNumberOfIndexes];
memset(&DEBUG_ChangedIndexes, 0, sizeof(DEBUG_ChangedIndexes));
#endif
//read each struct from the delta packet, until the NetIndex == the stop number
while (1)
{
// read the netIndex of this struct
newSnapshotNetIndex = NetBuffer_ReadBits(netBuffer, NETINDEX_BITS);
if (newSnapshotNetIndex < 0)
{
Net_Error_Disconnect("Net_ParseWalls: Invalid netIndex from client.");
return;
}
//================================================================
// DEBUG ONLY
if (DEBUG_FirstChangedIndex < 0)
{
DEBUG_FirstChangedIndex = newSnapshotNetIndex;
}
if (newSnapshotNetIndex != cSTOP_PARSING_CODE)
{
DEBUG_LastChangedIndex = newSnapshotNetIndex;
}
//----------------------------------------------------------------
if (newSnapshotNetIndex == (cSTOP_PARSING_CODE))
{
break;
}
if (newSnapshotNetIndex > cMaxStructIndex)
{
// this assert is only here to test the STOP_PARSING_CODE because we need that code to work for actors/sprites,
// since the number of walls/sectors does not change, it's safe to return here.
Bassert(0);
break;
}
newSnapshotStruct = &(newSnapshot->wall[newSnapshotNetIndex]);
oldSnapshotStruct = &(oldSnapshot->wall[oldSnapshotNetIndex]);
if (netBuffer->ReadCurByte > netBuffer->CurSize)
{
Net_Error_Disconnect("Net_ParseWalls: Reached end of buffer without finding a stop code.");
return;
}
// index up to the point where the changed structs start
while (oldSnapshotNetIndex < newSnapshotNetIndex)
{
// if it's not in the delta entity set, just use the previous snapshot's value
newSnapshot->wall[oldSnapshotNetIndex] = oldSnapshot->wall[oldSnapshotNetIndex];
oldSnapshotNetIndex++;
oldSnapshotStruct = &(oldSnapshot->wall[oldSnapshotNetIndex]);
}
// the struct referred to by oldindex is the same struct as the one referred to by newindex,
// compare the two structs
if (oldSnapshotNetIndex == newSnapshotNetIndex)
{
NetBuffer_ReadDeltaWall(netBuffer, oldSnapshotStruct, newSnapshotStruct, newSnapshotNetIndex);
oldSnapshotNetIndex++;
DEBUG_NumberOfStructsInPacket++;
#if DEBUG_CHANGEDINDEXES
DEBUG_ChangedIndexes[newSnapshotNetIndex] = 1;
#endif
}
}
// No more walls changed for the new snapshot, but there's still walls in the old snapshot.
//
// all of those old walls are unchanged. Copy all of these into the new snapshot.
while (oldSnapshotNetIndex < numwalls)
{
newSnapshot->wall[oldSnapshotNetIndex] = oldSnapshot->wall[oldSnapshotNetIndex];
oldSnapshotNetIndex++;
}
}
static void Net_ParseSectors(NetBuffer_t *netBuffer, netmapstate_t *oldSnapshot, netmapstate_t *newSnapshot)
{
int32_t newSnapshotNetIndex = 0;
int32_t oldSnapshotNetIndex = 0;
netSector_t *oldSnapshotStruct = NULL;
netSector_t *newSnapshotStruct = NULL;
const int32_t cMaxStructIndex = numsectors - 1;
NET_DEBUG_VAR int32_t DEBUG_PrevNewIndex = -1;
NET_DEBUG_VAR int32_t DEBUG_FirstChangedIndex = -1234;
NET_DEBUG_VAR int32_t DEBUG_LastChangedIndex = -1234;
#if DEBUG_CHANGEDINDEXES
NET_DEBUG_VAR int32_t DEBUG_ChangedIndexes[NetNumberOfIndexes];
memset(&DEBUG_ChangedIndexes, 0, sizeof(DEBUG_ChangedIndexes));
#endif
//read each struct from the delta packet, until the NetIndex == the stop number
while (1)
{
// read the netIndex of this struct
newSnapshotNetIndex = NetBuffer_ReadBits(netBuffer, NETINDEX_BITS);
//================================================================
// DEBUG ONLY
if (DEBUG_FirstChangedIndex < 0)
{
DEBUG_FirstChangedIndex = newSnapshotNetIndex;
}
if (newSnapshotNetIndex != cSTOP_PARSING_CODE)
{
DEBUG_LastChangedIndex = newSnapshotNetIndex;
}
//----------------------------------------------------------------
if (newSnapshotNetIndex < DEBUG_PrevNewIndex)
{
Net_Error_Disconnect("Error: Sectors were recieved out of order.");
}
DEBUG_PrevNewIndex = newSnapshotNetIndex;
if (newSnapshotNetIndex < 0)
{
Net_Error_Disconnect("Net_ParseSectors: Invalid netIndex from client.");
return;
}
if (newSnapshotNetIndex == (cSTOP_PARSING_CODE))
{
break;
}
if (newSnapshotNetIndex > cMaxStructIndex)
{
// this assert is only here to test the STOP_PARSING_CODE because we need that code to work for actors/sprites,
// since the number of walls/sectors does not change, it's safe to return here.
//
// also this can help catch corrupted packets
Bassert(0);
break;
}
newSnapshotStruct = &(newSnapshot->sector[newSnapshotNetIndex]);
oldSnapshotStruct = &(oldSnapshot->sector[oldSnapshotNetIndex]);
if (netBuffer->ReadCurByte > netBuffer->CurSize)
{
Net_Error_Disconnect("Net_ParseSectors: Reached end of buffer without finding a stop code.");
return;
}
// index up to the point where the changed structs start
while (oldSnapshotNetIndex < newSnapshotNetIndex)
{
// if it's not in the delta entity set, just use the previous snapshot's value
newSnapshot->sector[oldSnapshotNetIndex] = oldSnapshot->sector[oldSnapshotNetIndex];
oldSnapshotNetIndex++;
oldSnapshotStruct = &(oldSnapshot->sector[oldSnapshotNetIndex]);
}
// the struct referred to by oldindex is the same struct as the one referred to by newindex,
// compare the two structs
if (oldSnapshotNetIndex == newSnapshotNetIndex)
{
NetBuffer_ReadDeltaSector(netBuffer, oldSnapshotStruct, newSnapshotStruct, newSnapshotNetIndex);
oldSnapshotNetIndex++;
#if DEBUG_CHANGEDINDEXES
DEBUG_ChangedIndexes[newSnapshotNetIndex] = 1;
#endif
}
}
// No more sectors changed for the new snapshot, but there's still sectors in the old snapshot.
//
// all of those old sectors are unchanged. Copy all of these into the new snapshot.
while (oldSnapshotNetIndex < numsectors)
{
newSnapshot->sector[oldSnapshotNetIndex] = oldSnapshot->sector[oldSnapshotNetIndex];
oldSnapshotNetIndex++;
}
}
static void Net_ParseActors(NetBuffer_t *netBuffer, const netmapstate_t* oldSnapshot, netmapstate_t* newSnapshot)
{
// FROM PACKET: index into sprite[] (game arrays)
int32_t newActorIndex = 0;
// INCREMENTED: index into sprite[] (game arrays)
int32_t oldActorIndex = 0;
const netactor_t *oldSnapshotStruct = NULL;
netactor_t *newSnapshotStruct = NULL;
const int32_t cActorIndex_OutOfOldFrameActors = 99999;
NET_DEBUG_VAR int32_t DEBUG_FirstChangedIndex = -1234;
NET_DEBUG_VAR int32_t DEBUG_LastChangedIndex = -1234;
#if DEBUG_CHANGEDINDEXES
NET_DEBUG_VAR int32_t DEBUG_ChangedIndexes[NetNumberOfIndexes];
memset(&DEBUG_ChangedIndexes, 0, sizeof(DEBUG_ChangedIndexes));
#endif
newSnapshot->maxActorIndex = 0;
if (!oldSnapshot)
{
oldActorIndex = cActorIndex_OutOfOldFrameActors;
}
else
{
oldSnapshotStruct = &oldSnapshot->actor[oldActorIndex];
}
//read each struct from the delta packet, until the NetIndex == the stop number
//i.e., for each actor in the actors section of the packet...
while (1)
{
newActorIndex = NetBuffer_ReadBits(netBuffer, NETINDEX_BITS);
//================================================================
// DEBUG ONLY
if (DEBUG_FirstChangedIndex < 0)
{
DEBUG_FirstChangedIndex = newActorIndex;
}
if (newActorIndex != cSTOP_PARSING_CODE)
{
DEBUG_LastChangedIndex = newActorIndex;
}
//----------------------------------------------------------------
if (newActorIndex == cSTOP_PARSING_CODE)
{
//NOTE: This is okay, there is a while loop below to handle cases
// where there are unchanged actors after the last changed actor in the
// netbuffer.
break;
}
if (netBuffer->ReadCurByte > netBuffer->CurSize)
{
Net_Error_Disconnect("Net_ParseActors: Reached end of buffer without finding a stop code.");
}
if ((newActorIndex < 0) || (newActorIndex > MAXSPRITES))
{
Net_Error_Disconnect("Net_ParseActors: Invalid netIndex from client.");
}
// loop up to the point where the structs changed between the old frame and the new frame start
while (oldActorIndex < newActorIndex)
{
//actor is unchanged in the new snapshot, just *copy* the previous snapshot's value
newSnapshot->actor[oldActorIndex] = oldSnapshot->actor[oldActorIndex];
oldActorIndex++;
}
// NOTE that actors deleted for the new snapshot will be processed *here*. New actors that "fill in gaps"
// (rather than being added to the end) in the actor list will be processed here too.
if (oldActorIndex == newActorIndex)
{
oldSnapshotStruct = &(oldSnapshot->actor[oldActorIndex]);
newSnapshotStruct = &(newSnapshot->actor[newActorIndex]);
NetBuffer_ReadDeltaActor(netBuffer, oldSnapshotStruct, newSnapshotStruct, newActorIndex);
oldActorIndex++;
#if DEBUG_CHANGEDINDEXES
DEBUG_ChangedIndexes[newSnapshotStruct->netIndex] = 1;
#endif
continue; // because we handled the newActorIndex read from the packet.
}
// This actor is a newly inserted actor, either to be put in a space between
// two actors in the old snapshot, or after the end of the old snapshot.
//
// Note that the "no more oldframe entities" index constant runs this
if (oldActorIndex > newActorIndex)
{
newSnapshotStruct = &(newSnapshot->actor[newActorIndex]);
NetBuffer_ReadDeltaActor(netBuffer, &cNullNetActor, newSnapshotStruct, newActorIndex);
}
}
// No more actors changed for the new snapshot, but there's still actors in the old snapshot.
//
// All of those old actors are unchanged. Copy all of these into the new snapshot.
// Remember that deleting an actor counts as a "change".
while (oldActorIndex < MAXSPRITES)
{
newSnapshot->actor[oldActorIndex] = oldSnapshot->actor[oldActorIndex];
oldActorIndex++;
}
NET_75_CHECK++; // For now every snapshot will have MAXSPRITES entries
newSnapshot->maxActorIndex = MAXSPRITES;
}
static void NetBuffer_ReadWorldSnapshotFromBuffer(NetBuffer_t* netBuffer, netmapstate_t* oldSnapshot, netmapstate_t* newSnapshot)
{
// note that the order these functions are called should match the order that these structs are written to
// by the server
Net_ParseWalls(netBuffer, oldSnapshot, newSnapshot);
Net_ParseSectors(netBuffer, oldSnapshot, newSnapshot);
Net_ParseActors(netBuffer, oldSnapshot, newSnapshot);
}
static void Net_SendWorldUpdate(uint32_t fromRevisionNumber, uint32_t toRevisionNumber, int32_t sendToPlayerIndex)
{
if (sendToPlayerIndex == myconnectindex)
{
return;
}
Bassert(MAX_WORLDBUFFER == ARRAY_SIZE(tempnetbuf));
Bassert(NET_REVISIONS == ARRAY_SIZE(g_mapStateHistory));
uint32_t playerRevisionIsTooOld = (toRevisionNumber - fromRevisionNumber) > NET_REVISIONS;
// to avoid the client thinking that revision 2 is older than revision 0xFFFF_FFFF,
// send packets to take the client from the map's initial state until the client reports back
// that it's beyond that rollover threshold.
uint32_t revisionInRolloverState = (fromRevisionNumber > toRevisionNumber);
NetBuffer_t buffer;
NetBuffer_t* bufferPtr = &buffer;
// note: not enough stack memory to put the world data as a local variable
uint8_t* byteBuffer = &tempnetbuf[1];
uint32_t fromRevisionNumberToSend = 0x86753090;
netmapstate_t* toMapState = &g_mapStateHistory[toRevisionNumber % NET_REVISIONS];
netmapstate_t* fromMapState = NULL;
NET_75_CHECK++; // during the rollover state it might be a good idea to init the map state history?
// maybe not? I do init map states before using them, so it might not be needed.
if (playerRevisionIsTooOld || revisionInRolloverState)
{
fromMapState = &g_mapStartState;
fromRevisionNumberToSend = cInitialMapStateRevisionNumber;
}
else
{
uint32_t tFromRevisionIndex = fromRevisionNumber % NET_REVISIONS;
fromMapState = &g_mapStateHistory[tFromRevisionIndex];
fromRevisionNumberToSend = fromRevisionNumber;
}
Bmemset(byteBuffer, 0, sizeof(tempnetbuf) - 1);
tempnetbuf[0] = PACKET_WORLD_UPDATE;
NetBuffer_Init(bufferPtr, byteBuffer, MAX_WORLDBUFFER);
NetBuffer_WriteDword(bufferPtr, fromRevisionNumberToSend);
NetBuffer_WriteDword(bufferPtr, toRevisionNumber);
Net_WriteWorldToBuffer(bufferPtr, fromMapState, toMapState);
if (sendToPlayerIndex > ((int32_t) g_netServer->peerCount))
{
Net_Error_Disconnect("No peer for player.");
return;
}
// in the future we could probably use these flags for enet_peer_send, for the world updates
EDUKE32_UNUSED const ENetPacketFlag optimizedFlags = (ENetPacketFlag)(ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
NET_75_CHECK++; // HACK: I Really need to keep the peer with the player instead of assuming that the peer index is the same as the (player index - 1)
ENetPeer *const tCurrentPeer = &g_netServer->peers[sendToPlayerIndex - 1];
enet_peer_send(tCurrentPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf, bufferPtr->CurSize + 1, 0));
Dbg_PacketSent(PACKET_WORLD_UPDATE);
}
static void Net_CopySnapshotToGameArrays(netmapstate_t* srv_snapshot, netmapstate_t* cl_snapshot)
{
int32_t index;
// this isn't just copying the entries that "changed" into the game arrays,
// remember that we need to correct any incorrect guesses the client made.
// don't do memcpy either, e.g., sizeof(netWall_t) != sizeof(walltype)
for (index = 0; index < numwalls; index++)
{
netWall_t* srvWall = &(srv_snapshot->wall[index]);
netWall_t* clWall = &(cl_snapshot->wall[index]);
int status = memcmp(srvWall, clWall, sizeof(netWall_t));
if(status == 0)
{
if(g_enableClientInterpolationCheck)
{
// only copy in the server's (old) value if the client's interpolation up to that point was incorrect
continue;
}
}
walltype* gameWall = &(wall[index]);
Net_CopyWallFromNet(srvWall, gameWall);
}
for (index = 0; index < numsectors; index++)
{
netSector_t* srvSector = &(srv_snapshot->sector[index]);
netSector_t* clSector = &(cl_snapshot->sector[index]);
int status = memcmp(srvSector, clSector, sizeof(netSector_t));
if(status == 0)
{
if(g_enableClientInterpolationCheck)
{
continue;
}
}
sectortype* gameSector = &(sector[index]);
Net_CopySectorFromNet(srvSector, gameSector);
}
Net_CopyActorsToGameArrays(srv_snapshot, cl_snapshot);
}
// clients only
static void Net_ReadWorldUpdate(uint8_t* packetData, int32_t packetSize)
{
if (!g_netClient)
{
return;
}
NetBuffer_t buffer;
NetBuffer_t* bufferPtr = &buffer;
uint8_t* dataStartAddr = packetData + 1;
NET_DEBUG_VAR int16_t DEBUG_NoMapLoaded = ((numwalls < 1) || (numsectors < 1));
NET_DEBUG_VAR int32_t DEBUG_InitialSnapshotNotSet = (g_mapStartState.sector[0].wallnum <= 0);
if (!ClientPlayerReady)
{
return;
}
NetBuffer_Init(bufferPtr, dataStartAddr, MAX_WORLDBUFFER);
bufferPtr->CurSize = packetSize - 1;
uint32_t packetFromRevisionNumber = NetBuffer_ReadDWord(bufferPtr);
uint32_t packetToRevisionNumber = NetBuffer_ReadDWord(bufferPtr);
uint32_t from_IsInitialState = (packetFromRevisionNumber == cInitialMapStateRevisionNumber);
uint32_t clientRevisionIsTooOld = (packetToRevisionNumber - g_netMapRevisionNumber) > NET_REVISIONS;
netmapstate_t* fromMapState = NULL;
if (clientRevisionIsTooOld && !from_IsInitialState)
{
// this is actually not a major problem, an alternative way to handle this would be to just ignore the packet and hope the
// server sends a diff from the initial revision eventually.
//
// NOTE: If you get this bug on connect it really is a problem, the client never seems to get updated to the initial revision when
// this happens. It seems like if you don't wat enough time before connecting again after a crash you will get this to happen.
//
// actually I've seen that happen even when this error didn't happen.
Net_Error_Disconnect("Internal Error: Net_ReadWorldUpdate(): Client From map state too old, but server did not send snapshot from initial state.");
return;
}
if (from_IsInitialState)
{
// clients must always accept the initial map state as a snapshot,
// If a client's current revision is too old, the server will send them
// a snapshot to update them from the initial state instead of the
// client's last known state.
fromMapState = &g_mapStartState;
}
else if (packetToRevisionNumber <= g_netMapRevisionNumber)
{
// clients should just ignore revisions that are older than their current revision
// (packets can arrive out of order). Note that the server will send cInitialMapStateRevisionNumber
// as the "From" index when the revision counter rolls over.
return;
}
else
{
fromMapState = &g_mapStateHistory[packetFromRevisionNumber % NET_REVISIONS];
}
netmapstate_t* toMapState = &g_mapStateHistory[packetToRevisionNumber % NET_REVISIONS];
netmapstate_t* clMapState = &g_cl_InterpolatedMapStateHistory[packetToRevisionNumber % NET_REVISIONS];
toMapState->revisionNumber = packetToRevisionNumber;
NET_DEBUG_VAR uint32_t DEBUG_OldClientRevision = g_netMapRevisionNumber;
Bassert(fromMapState);
NetBuffer_ReadWorldSnapshotFromBuffer(bufferPtr, fromMapState, toMapState);
g_netMapRevisionNumber = packetToRevisionNumber;
Net_CopySnapshotToGameArrays(toMapState, clMapState);
}
// handles revision rollover
static uint32_t Net_GetNextRevisionNumber(uint32_t currentNumber)
{
if(currentNumber == UINT32_MAX)
{
// we should be able to recover from this
OSD_Printf("Net_SendMapUpdates(): [Note] Map state revision number overflow.");
return cStartingRevisionIndex;
}
return (currentNumber + 1);
}
// ---------------------------------------------------------------------------------------------------------------
// Externally accessible functions
// ---------------------------------------------------------------------------------------------------------------
void Net_WaitForInitialSnapshot()
{
while (g_netMapRevisionNumber < cStartingRevisionIndex)
{
G_HandleAsync();
}
}
// this is mostly just here to put a breakpoint on, use conditional breakpoints to stop only on particular types of packets
int32_t Dbg_PacketSent(enum DukePacket_t iPacketType)
{
return iPacketType + 1;
}
// If failed is true, that means the vote lost. Otherwise it was cancelled by the client who initiated it.
void Net_SendMapVoteCancel(int32_t failed)
{
// Only the server or the client that initiated the vote can cancel the vote
if (g_netClient && voting != myconnectindex)
{
return;
}
tempbuf[0] = PACKET_MAP_VOTE_CANCEL;
tempbuf[1] = myconnectindex;
// If we're forwarding a cancelled message, change the connection index to the one who cancelled it.
if (g_netServer && !failed)
{
tempbuf[1] = voting;
}
voting = -1;
Dbg_PacketSent(PACKET_MAP_VOTE_CANCEL);
if (g_netClient)
{
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
}
else if (g_netServer)
{
enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
}
}
void Net_SendMapVote(int32_t votefor)
{
voting = -1;
g_player[myconnectindex].gotvote = 1;
g_player[myconnectindex].vote = votefor;
tempbuf[0] = PACKET_MAP_VOTE;
tempbuf[1] = myconnectindex;
tempbuf[2] = votefor;
tempbuf[3] = myconnectindex;
Dbg_PacketSent(PACKET_MAP_VOTE);
if (g_netClient)
{
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 4, ENET_PACKET_FLAG_RELIABLE));
}
else if (g_netServer)
{
enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempbuf[0], 4, ENET_PACKET_FLAG_RELIABLE));
}
Net_CheckForEnoughVotes();
}
void Net_SendMapVoteInitiate(void)
{
newgame_t newgame;
if (!g_netClient)
{
return;
}
voting = myconnectindex;
newgame.header = PACKET_MAP_VOTE_INITIATE;
newgame.connection = myconnectindex;
Net_FillNewGame(&newgame, 1);
Dbg_PacketSent(PACKET_MAP_VOTE_INITIATE);
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE));
}
void Net_SendNewGame(int32_t frommenu, ENetPeer *peer)
{
newgame_t newgame;
newgame.header = PACKET_NEW_GAME;
Net_FillNewGame(&newgame, frommenu);
Dbg_PacketSent(PACKET_NEW_GAME);
if (peer)
{
enet_peer_send(peer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE));
}
else
{
enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&newgame, sizeof(newgame_t), ENET_PACKET_FLAG_RELIABLE));
}
Net_ResetPlayerReady();
}
void Net_FillNewGame(newgame_t *newgame, int32_t frommenu)
{
if (frommenu)
{
newgame->level_number = ud.m_level_number;
newgame->volume_number = ud.m_volume_number;
newgame->player_skill = ud.m_player_skill;
newgame->monsters_off = ud.m_monsters_off;
newgame->respawn_monsters = ud.m_respawn_monsters;
newgame->respawn_items = ud.m_respawn_items;
newgame->respawn_inventory = ud.m_respawn_inventory;
newgame->ffire = ud.m_ffire;
newgame->noexits = ud.m_noexits;
newgame->coop = ud.m_coop;
}
else
{
newgame->level_number = ud.level_number;
newgame->volume_number = ud.volume_number;
newgame->player_skill = ud.player_skill;
newgame->monsters_off = ud.monsters_off;
newgame->respawn_monsters = ud.respawn_monsters;
newgame->respawn_items = ud.respawn_items;
newgame->respawn_inventory = ud.respawn_inventory;
newgame->ffire = ud.ffire;
newgame->noexits = ud.noexits;
newgame->coop = ud.coop;
}
}
// store the client's history so that we can go back and look and see if the client interpolated properly
// (the server snapshots are always older than the client's current game tic)
void Net_StoreClientState(void)
{
if(!g_netClient)
{
return;
}
// don't store client revisions if we don't even have a server revision
if(g_netMapRevisionNumber < cStartingRevisionIndex)
{
return;
}
g_cl_InterpolatedRevision = Net_GetNextRevisionNumber(g_cl_InterpolatedRevision);
netmapstate_t* currentMapState = &g_cl_InterpolatedMapStateHistory[g_cl_InterpolatedRevision % NET_REVISIONS];
Net_InitMapState(currentMapState);
Net_AddWorldToSnapshot(currentMapState);
currentMapState->revisionNumber = g_cl_InterpolatedRevision;
}
void Net_SendMapUpdate(void)
{
if (g_netClient || !g_netServer || numplayers < 2)
{
return;
}
g_netMapRevisionNumber = Net_GetNextRevisionNumber(g_netMapRevisionNumber);
netmapstate_t* toMapState = &g_mapStateHistory[g_netMapRevisionNumber % NET_REVISIONS];
Net_InitMapState(toMapState);
Net_AddWorldToSnapshot(toMapState);
toMapState->revisionNumber = g_netMapRevisionNumber;
int32_t playerIndex = 0;
for (TRAVERSE_CONNECT(playerIndex))
{
if (playerIndex == myconnectindex)
{
// there's no point in the server sending itself a snapshot.
continue;
}
uint32_t playerRevisionNumber = g_player[playerIndex].revision;
Net_SendWorldUpdate(playerRevisionNumber, g_netMapRevisionNumber, playerIndex);
}
}
void DumpMapStateHistory()
{
const char* fileName = NULL;
if(g_netClient)
{
fileName = "CL_MapStates.bin";
}
else
{
fileName = "SRV_MapStates.bin";
}
FILE* mapStatesFile = fopen(fileName, "wb");
// write the null map state (it should never, ever be changed, but just for completeness sake
// fwrite(&NullMapState, sizeof(NullMapState), 1, mapStatesFile);
fwrite(&g_mapStartState, sizeof(g_mapStartState), 1, mapStatesFile);
fwrite(&g_mapStateHistory[0], sizeof(g_mapStateHistory), 1, mapStatesFile);
OSD_Printf("Dumped map states to %s.\n", fileName);
fclose(mapStatesFile);
mapStatesFile = NULL;
}
void Net_SpawnPlayer(int32_t player)
{
int32_t byteOffset = 0;
packbuf[byteOffset++] = PACKET_PLAYER_SPAWN;
packbuf[byteOffset++] = player;
Bmemcpy(&packbuf[byteOffset], &g_player[player].ps->pos.x, sizeof(vec3_t) * 2);
byteOffset += sizeof(vec3_t) * 2;
packbuf[byteOffset++] = 0;
enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], byteOffset, ENET_PACKET_FLAG_RELIABLE));
Dbg_PacketSent(PACKET_PLAYER_SPAWN);
}
void Net_WaitForServer(void)
{
int32_t serverReady = g_player[0].pingcnt;
if (numplayers < 2 || g_netServer)
return;
P_SetGamePalette(g_player[myconnectindex].ps, TITLEPAL, 8 + 2 + 1);
do
{
if (quitevent || keystatus[1])
G_GameExit("");
if (G_FPSLimit())
{
display_betascreen();
gametext_center_shade(170, "Waiting for server", 14);
}
// 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
///</summary>
void Net_AddWorldToInitialSnapshot()
{
Net_AddWorldToSnapshot(&g_mapStartState);
}
void Net_SendClientInfo(void)
{
int32_t i, l;
for (l = 0; (unsigned)l < sizeof(szPlayerName) - 1; l++) g_player[myconnectindex].user_name[l] = szPlayerName[l];
if (numplayers < 2)
return;
tempnetbuf[0] = PACKET_CLIENT_INFO;
l = 1;
// null terminated player name to send
for (i = 0; szPlayerName[i]; i++)
{
tempnetbuf[l++] = szPlayerName[i];
}
tempnetbuf[l++] = 0;
tempnetbuf[l++] = g_player[myconnectindex].ps->aim_mode = ud.mouseaiming;
tempnetbuf[l++] = g_player[myconnectindex].ps->auto_aim = ud.config.AutoAim;
tempnetbuf[l++] = g_player[myconnectindex].ps->weaponswitch = ud.weaponswitch;
tempnetbuf[l++] = g_player[myconnectindex].ps->palookup = g_player[myconnectindex].pcolor = ud.color;
tempnetbuf[l++] = g_player[myconnectindex].pteam = ud.team;
for (i = 0; i < 10; i++)
{
g_player[myconnectindex].wchoice[i] = g_player[0].wchoice[i];
tempnetbuf[l++] = (uint8_t)g_player[0].wchoice[i];
}
tempnetbuf[l++] = myconnectindex;
Dbg_PacketSent(PACKET_CLIENT_INFO);
if (g_netClient)
{
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE));
}
else if (g_netServer)
{
enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], l, ENET_PACKET_FLAG_RELIABLE));
}
}
void Net_SendUserMapName(void)
{
if (numplayers < 2)
return;
packbuf[0] = PACKET_USER_MAP;
Bcorrectfilename(boardfilename, 0);
// user map name is sent with a NUL at the end
int32_t j = Bstrlen(boardfilename) + 1;
Bmemcpy(&packbuf[1], boardfilename, j);
j++;
packbuf[j++] = myconnectindex;
Dbg_PacketSent(PACKET_USER_MAP);
if (g_netClient)
{
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], j, ENET_PACKET_FLAG_RELIABLE));
}
else if (g_netServer)
{
enet_host_broadcast(g_netServer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], j, ENET_PACKET_FLAG_RELIABLE));
}
}
// Insert a sprite from STAT_NETALLOC, and add it to the scratch pad list if we're a client.
int32_t Net_InsertSprite(int32_t sect, int32_t stat)
{
if ((!g_netServer) && (!g_netClient))
{
return insertsprite(sect, stat);
}
int32_t i = Net_DoInsertSprite(sect, stat);
if (i < 0)
{
return i;
}
if (g_netClient)
{
Net_InsertScratchPadSprite(i);
}
return i;
}
void Net_DeleteSprite(int32_t spritenum)
{
// [75] later on, real clientside sprites (not scratchpad or server side) can be just deleted
// using deletesprite without worrying about linked lists or sprite indexes going out of sync.
// [75] for most cases, only the server deletes sprites, clients just set their sprites invisible and to
// the null picnum so no CON gets executed
if ((!g_netServer) && (!g_netClient))
{
deletesprite(spritenum);
return;
}
if (g_netClient)
{
sprite[spritenum].cstat = 32768;
sprite[spritenum].picnum = 0;
return;
}
Net_DoDeleteSprite(spritenum);
}
// Send various player related updates (server -> client)
void Net_SendServerUpdates(void)
{
int16_t i;
uint8_t * updatebuf;
serverupdate_t serverupdate;
serverplayerupdate_t playerupdate;
input_t * osyn = (input_t *)&inputfifo[1][0];
input_t * nsyn = (input_t *)&inputfifo[0][0];
ticrandomseed = randomseed;
if (g_netServer)
{
Bmemcpy(&osyn[0], &nsyn[0], sizeof(input_t));
}
if (!g_netServer || numplayers < 2)
{
return;
}
serverupdate.header = PACKET_MASTER_TO_SLAVE;
serverupdate.seed = ticrandomseed;
serverupdate.nsyn = *nsyn;
serverupdate.pause_on = ud.pause_on;
serverupdate.numplayers = 0;
updatebuf = tempnetbuf + sizeof(serverupdate_t);
for (TRAVERSE_CONNECT(i))
{
if (g_player[i].playerquitflag == 0)
{
continue;
}
Net_FillPlayerUpdate(&playerupdate.player, i);
playerupdate.gotweapon = g_player[i].ps->gotweapon;
playerupdate.kickback_pic = g_player[i].ps->kickback_pic;
Bmemcpy(playerupdate.frags, g_player[i].frags, sizeof(playerupdate.frags));
Bmemcpy(playerupdate.inv_amount, g_player[i].ps->inv_amount, sizeof(playerupdate.inv_amount));
Bmemcpy(playerupdate.ammo_amount, g_player[i].ps->ammo_amount, sizeof(playerupdate.ammo_amount));
playerupdate.curr_weapon = g_player[i].ps->curr_weapon;
playerupdate.last_weapon = g_player[i].ps->last_weapon;
playerupdate.wantweaponfire = g_player[i].ps->wantweaponfire;
playerupdate.weapon_pos = g_player[i].ps->weapon_pos;
playerupdate.frag_ps = g_player[i].ps->frag_ps;
playerupdate.frag = g_player[i].ps->frag;
playerupdate.fraggedself = g_player[i].ps->fraggedself;
playerupdate.last_extra = g_player[i].ps->last_extra;
playerupdate.ping = g_player[i].ping;
playerupdate.newowner = g_player[i].ps->newowner;
Bmemcpy(updatebuf, &playerupdate, sizeof(serverplayerupdate_t));
updatebuf += sizeof(serverplayerupdate_t);
serverupdate.numplayers++;
}
if (serverupdate.numplayers == 0)
{
return;
}
Bmemcpy(tempnetbuf, &serverupdate, sizeof(serverupdate_t));
enet_host_broadcast(
g_netServer, CHAN_MOVE,
enet_packet_create(&tempnetbuf[0], sizeof(serverupdate_t) + (serverupdate.numplayers * sizeof(serverplayerupdate_t)), 0));
Dbg_PacketSent(PACKET_MASTER_TO_SLAVE);
}
void Net_SendClientUpdate(void)
{
clientupdate_t update;
update.header = PACKET_SLAVE_TO_MASTER;
update.RevisionNumber = g_netMapRevisionNumber;
update.nsyn = inputfifo[0][myconnectindex];
Net_FillPlayerUpdate(&update.player, myconnectindex);
enet_peer_send(g_netClientPeer, CHAN_MOVE, enet_packet_create(&update, sizeof(clientupdate_t), 0));
Dbg_PacketSent(PACKET_SLAVE_TO_MASTER);
}
void Net_SendMessage(void)
{
if (g_player[myconnectindex].ps->gm & MODE_SENDTOWHOM)
{
int32_t i, j;
if (g_chatPlayer != -1 || ud.multimode < 3)
{
tempbuf[0] = PACKET_MESSAGE;
tempbuf[2] = 0;
recbuf[0] = 0;
if (ud.multimode < 3)
g_chatPlayer = 2;
if (typebuf[0] == '/' && Btoupper(typebuf[1]) == 'M' && Btoupper(typebuf[2]) == 'E')
{
Bstrcat(recbuf, "* ");
i = 3, j = Bstrlen(typebuf);
Bstrcpy(tempbuf, typebuf);
while (i < j)
{
typebuf[i - 3] = tempbuf[i];
i++;
}
typebuf[i - 3] = '\0';
Bstrcat(recbuf, g_player[myconnectindex].user_name);
}
else
{
Bstrcat(recbuf, g_player[myconnectindex].user_name);
Bstrcat(recbuf, ": ");
}
Bstrcat(recbuf, "^00");
Bstrcat(recbuf, typebuf);
j = Bstrlen(recbuf);
recbuf[j] = 0;
Bstrcat(tempbuf + 2, recbuf);
if (g_chatPlayer >= ud.multimode)
{
tempbuf[1] = 255;
tempbuf[j + 2] = myconnectindex;
j++;
if (g_netServer)
enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(&tempbuf[0], j + 2, 0));
else if (g_netClient)
enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(&tempbuf[0], j + 2, 0));
G_AddUserQuote(recbuf);
}
g_chatPlayer = -1;
g_player[myconnectindex].ps->gm &= ~(MODE_TYPE | MODE_SENDTOWHOM);
}
else if (g_chatPlayer == -1)
{
j = 50;
gametext_center(j, "Send message to...");
j += 8;
for (TRAVERSE_CONNECT(i))
{
if (i == myconnectindex)
{
minitextshade((320 >> 1) - 40 + 1, j + 1, "A/ENTER - ALL", 26, 0, 2 + 8 + 16);
minitext((320 >> 1) - 40, j, "A/ENTER - ALL", 0, 2 + 8 + 16);
j += 7;
}
else
{
Bsprintf(recbuf, " %d - %s", i + 1, g_player[i].user_name);
minitextshade((320 >> 1) - 40 - 6 + 1, j + 1, recbuf, 26, 0, 2 + 8 + 16);
minitext((320 >> 1) - 40 - 6, j, recbuf, 0, 2 + 8 + 16);
j += 7;
}
}
minitextshade((320 >> 1) - 40 - 4 + 1, j + 1, " ESC - Abort", 26, 0, 2 + 8 + 16);
minitext((320 >> 1) - 40 - 4, j, " ESC - Abort", 0, 2 + 8 + 16);
j += 7;
mpgametext(mpgametext_x, ud.screen_size > 0 ? (200 - 45) << 16 : (200 - 8) << 16, typebuf, 0, 0, 0, 0);
if (KB_KeyWaiting())
{
i = KB_GetCh();
if (i == 'A' || i == 'a' || i == 13)
g_chatPlayer = ud.multimode;
else if (i >= '1' || i <= (ud.multimode + '1'))
g_chatPlayer = i - '1';
else
{
g_chatPlayer = ud.multimode;
if (i == 27)
{
g_player[myconnectindex].ps->gm &= ~(MODE_TYPE | MODE_SENDTOWHOM);
g_chatPlayer = -1;
}
else
typebuf[0] = 0;
}
KB_ClearKeyDown(sc_1);
KB_ClearKeyDown(sc_2);
KB_ClearKeyDown(sc_3);
KB_ClearKeyDown(sc_4);
KB_ClearKeyDown(sc_5);
KB_ClearKeyDown(sc_6);
KB_ClearKeyDown(sc_7);
KB_ClearKeyDown(sc_8);
KB_ClearKeyDown(sc_A);
KB_ClearKeyDown(sc_Escape);
KB_ClearKeyDown(sc_Enter);
}
}
}
else
{
int32_t const hitstate = I_EnterText(typebuf, 120, 0);
int32_t const y = ud.screen_size > 1 ? (200 - 58) << 16 : (200 - 35) << 16;
int32_t const width = mpgametextsize(typebuf, TEXT_LITERALESCAPE).x;
int32_t const fullwidth = width + textsc((tilesiz[SPINNINGNUKEICON].x << 15) + (2 << 16));
int32_t const text_x = fullwidth >= (320 << 16) ? (320 << 16) - fullwidth : mpgametext_x;
mpgametext(text_x, y, typebuf, 1, 2 | 8 | 16 | ROTATESPRITE_FULL16, 0, TEXT_YCENTER | TEXT_LITERALESCAPE);
int32_t const cursor_x = text_x + width + textsc((tilesiz[SPINNINGNUKEICON].x << 14) + (1 << 16));
rotatesprite_fs(cursor_x, y, textsc(32768), 0, SPINNINGNUKEICON + ((totalclock >> 3) % 7),
4 - (sintable[(totalclock << 4) & 2047] >> 11), 0, 2 | 8);
if (hitstate == 1)
{
KB_ClearKeyDown(sc_Enter);
if (Bstrlen(typebuf) == 0)
{
g_player[myconnectindex].ps->gm &= ~(MODE_TYPE | MODE_SENDTOWHOM);
return;
}
if (ud.automsg)
{
if (SHIFTS_IS_PRESSED)
g_chatPlayer = -1;
else
g_chatPlayer = ud.multimode;
}
g_player[myconnectindex].ps->gm |= MODE_SENDTOWHOM;
}
else if (hitstate == -1)
g_player[myconnectindex].ps->gm &= ~(MODE_TYPE | MODE_SENDTOWHOM);
else
pub = NUMPAGES;
}
}
void Net_InitMapStateHistory()
{
int32_t mapStateIndex = 0;
for (mapStateIndex = 0; mapStateIndex < NET_REVISIONS; mapStateIndex++)
{
netmapstate_t *mapState = &g_mapStateHistory[mapStateIndex];
netmapstate_t *clState = &g_cl_InterpolatedMapStateHistory[mapStateIndex];
Net_InitMapState(mapState);
Net_InitMapState(clState);
}
Net_InitMapState(&g_mapStartState);
g_mapStartState.revisionNumber = cInitialMapStateRevisionNumber;
g_netMapRevisionNumber = cInitialMapStateRevisionNumber; // Net_InitMapStateHistory()
g_cl_InterpolatedRevision = cInitialMapStateRevisionNumber;
}
void Net_StartNewGame()
{
Net_ResetPlayers();
Net_ExtractNewGame(&pendingnewgame, 0);
G_NewGame(ud.volume_number, ud.level_number, ud.player_skill);
ud.coop = ud.m_coop;
if (G_EnterLevel(MODE_GAME))
{
G_BackToMenu();
}
}
void Net_NotifyNewGame()
{
int32_t spriteIndex;
int32_t statIndex;
int32_t numSprites = 0;
int32_t numSpritesToNetAlloc = 0;
if (!g_netServer && !g_netClient)
{
return;
}
// Grab the total number of sprites at level load
for (statIndex = 0; statIndex < MAXSTATUS; ++statIndex)
{
spriteIndex = headspritestat[statIndex];
for (; spriteIndex >= 0; spriteIndex = nextspritestat[spriteIndex])
{
numSprites++;
}
}
// Take half of the leftover sprites and allocate them for the network's nefarious purposes.
numSpritesToNetAlloc = (MAXSPRITES - numSprites) / 2;
for (spriteIndex = 0; spriteIndex < numSpritesToNetAlloc; ++spriteIndex)
{
int32_t newSprite = insertspritestat(STAT_NETALLOC);
sprite[newSprite].sectnum = MAXSECTORS;
Numsprites++;
}
//[75] Note: DON'T set the initial map state or initialize the map state history in the packet code,
// The client didn't load the map until G_EnterLevel
}
#endif
//-------------------------------------------------------------------------------------------------