mirror of
https://github.com/ZDoom/raze-gles.git
synced 2025-02-09 23:10:49 +00:00
Using that higher precision, interpolate at a higher granularity. Further, truncate the target interpolation time to vertical blank boundaries to avoid producing temporal artifacts. Fix issues caused by interpolation calculations being handled differently in multiple places (and fix cases where smoothratios were being thrown away only to be redone without checking all proper conditions). Ensure ClockTicks changes do not break other targets (EKenBuild, VoidSW), but note any interpolation there is not similarly updated. git-svn-id: https://svn.eduke32.com/eduke32@8050 1a8010ca-5511-0410-912e-c29ae57300e0 # Conflicts: # source/build/src/build.cpp # source/build/src/sdlayer.cpp
5251 lines
158 KiB
C++
5251 lines
158 KiB
C++
//-------------------------------------------------------------------------
|
|
/*
|
|
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"
|
|
#include "input.h"
|
|
|
|
#include "enet/enet.h"
|
|
#include "lz4.h"
|
|
#include "crc32.h"
|
|
|
|
#include "vfs.h"
|
|
|
|
// Data needed even if netcode is disabled
|
|
ENetHost *g_netServer = NULL;
|
|
ENetHost *g_netClient = NULL;
|
|
ENetPeer *g_netClientPeer = NULL;
|
|
enet_uint16 g_netPort = 23513;
|
|
int32_t g_netDisconnect = 0;
|
|
char g_netPassword[32];
|
|
int32_t g_networkMode = NET_CLIENT;
|
|
|
|
// to support (gcc only) -f-strict-aliasing, the netcode needs to specify that its 32 bit chunks to and from the
|
|
// packet code should not be subject to strict aliasing optimizations
|
|
// it would appear that only GCC does this, Clang doesn't seem to and Microsoft Visual C++ definitely doesn't.
|
|
#ifdef __GNUC__
|
|
#define TYPE_PUNNED __attribute__((__may_alias__))
|
|
#else
|
|
#define TYPE_PUNNED
|
|
#endif
|
|
|
|
typedef TYPE_PUNNED int32_t NetChunk32;
|
|
|
|
|
|
// Unfortunately faketimerhandler needs extra "help" because the Build Engine source doesn't include network.h.
|
|
#ifdef NETCODE_DISABLE
|
|
void faketimerhandler(void)
|
|
{
|
|
;
|
|
}
|
|
#else
|
|
void faketimerhandler(void)
|
|
{
|
|
if (g_netServer == NULL && g_netClient == NULL)
|
|
return;
|
|
|
|
enet_host_service(g_netServer ? g_netServer : g_netClient, NULL, 0);
|
|
}
|
|
|
|
static void Net_Disconnect(void);
|
|
static void Net_HandleClientPackets(void);
|
|
static void Net_HandleServerPackets(void);
|
|
#endif
|
|
|
|
void Net_GetPackets(void)
|
|
{
|
|
timerUpdate();
|
|
MUSIC_Update();
|
|
S_Update();
|
|
|
|
G_HandleSpecialKeys();
|
|
|
|
#ifndef NETCODE_DISABLE
|
|
if (g_netDisconnect)
|
|
{
|
|
Net_Disconnect();
|
|
g_netDisconnect = 0;
|
|
|
|
if (g_gameQuit)
|
|
G_GameExit(" ");
|
|
|
|
return;
|
|
}
|
|
|
|
if (g_netServer)
|
|
{
|
|
Net_HandleClientPackets();
|
|
}
|
|
else if (g_netClient)
|
|
{
|
|
Net_HandleServerPackets();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// (the rest of the file)
|
|
#ifndef NETCODE_DISABLE
|
|
|
|
// this attribute is for variables that are only used to highlight interesting conditions in the code
|
|
// when I'm in a debugger.
|
|
#define NET_DEBUG_VAR EDUKE32_UNUSED
|
|
|
|
// internal data
|
|
#define SNAPSHOTS_TO_SAVE 32
|
|
#define MAX_SNAPSHOT_ACTORS 256
|
|
#define MAX_SNAPSHOT_WALLS MAXWALLS // 16384
|
|
#define MAX_SNAPSHOT_SECTORS MAXSECTORS // 4096
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Server Update Packets
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct serverupdate_s
|
|
{
|
|
uint8_t header;
|
|
uint8_t numplayers;
|
|
input_t nsyn;
|
|
int32_t seed;
|
|
int16_t pause_on;
|
|
} serverupdate_t;
|
|
#pragma pack(pop)
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct serverplayerupdate_s
|
|
{
|
|
uint8_t extra;
|
|
int16_t cstat;
|
|
uint16_t owner;
|
|
uint16_t picnum;
|
|
uint16_t gotweapon;
|
|
uint8_t kickback_pic;
|
|
uint8_t frags[MAXPLAYERS];
|
|
int16_t inv_amount[GET_MAX];
|
|
int16_t ammo_amount[MAX_WEAPONS];
|
|
|
|
uint8_t curr_weapon;
|
|
uint8_t last_weapon;
|
|
uint8_t wantweaponfire;
|
|
uint8_t weapon_pos;
|
|
uint8_t frag_ps;
|
|
uint8_t frag;
|
|
uint8_t fraggedself;
|
|
uint8_t last_extra;
|
|
uint8_t pal;
|
|
uint16_t ping;
|
|
uint16_t newowner;
|
|
|
|
playerupdate_t player;
|
|
} serverplayerupdate_t;
|
|
#pragma pack(pop)
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct clientupdate_s
|
|
{
|
|
uint8_t header;
|
|
int32_t RevisionNumber;
|
|
input_t nsyn;
|
|
playerupdate_t player;
|
|
} clientupdate_t;
|
|
#pragma pack(pop)
|
|
|
|
|
|
// Client player ready for world updates (player spawned and ready)
|
|
static int ClientPlayerReady = 0;
|
|
|
|
// all zero NetActor
|
|
static const netactor_t cNullNetActor = netactor_t();
|
|
|
|
// all zero NetWall
|
|
static const netWall_t cNullNetWall = netWall_t();
|
|
|
|
// all zero NetSector
|
|
static const netSector_t cNullNetSector = netSector_t();
|
|
|
|
// the revision number reserved for the state of the map when it was loaded from the .map file.
|
|
// NOTE: Don't confuse this with a revision index. Index 0 into the snapshot history is NOT the initial map state.
|
|
// g_MapStateHistory[0] could be revision 0, revision 64, revision 128, etc...
|
|
static const uint32_t cInitialMapStateRevisionNumber = 0;
|
|
// the revision array starts here instead of zero (may as well have the initial map state be revision 0)...
|
|
static const uint32_t cStartingRevisionIndex = 1;
|
|
|
|
static const int32_t cLocSprite_DeletedSpriteStat = MAXSTATUS;
|
|
#ifdef CURRENTLY_UNUSED
|
|
static const int32_t cNetSprite_DeletedSpriteStat = STAT_NETALLOC;
|
|
#endif
|
|
|
|
//[75] When a client attempts to allocate a sprite during the game loop, which is not defined as a clientside sprite,
|
|
// the insert will go through, and the sprite will be put on this list.
|
|
//
|
|
// every time the client gets a new snapshot, this entire list will be deleted
|
|
|
|
static int32_t headscratchpadsprite = -1;
|
|
static int32_t nextscratchpadsprite[MAXSPRITES];
|
|
|
|
|
|
static char recbuf[180];
|
|
static int32_t g_chatPlayer = -1;
|
|
|
|
// note that NETINDEX_BITS must be large enough to store the largest of
|
|
// MAXWALLS, MAXSECTORS, and MAXSPRITES, plus one bit so that the stop
|
|
// code won't be mistaken for a valid index
|
|
//
|
|
// as of 5/4/2016 MAXSPRITES is 16384, which technically only needs 14 bits but I'm going to
|
|
// set this to 16 + 1 to make it a more even number
|
|
//
|
|
// also NETINDEX_BITS should not exceed 32
|
|
#define NETINDEX_BITS (16 + 1)
|
|
|
|
|
|
// worst case: 2 bits per field per stuct if every field in every struct is changed
|
|
#define WORLD_CHANGEBITSSIZE \
|
|
(MAXWALLS * ARRAY_SIZE(WallFields) * 2) + (MAXSECTORS * ARRAY_SIZE(SectorFields) * 2) + (MAXSPRITES * ARRAY_SIZE(ActorFields) * 2)
|
|
|
|
// each changed entry has a netindex + stop codes bits to indicate whether a field
|
|
// changed/zeroed convert to bytes....
|
|
#define WORLD_OVERHEADSIZE (((MAXSECTORS + MAXWALLS + MAXSPRITES + 3) * NETINDEX_BITS + WORLD_CHANGEBITSSIZE) >> 8) + 1
|
|
|
|
|
|
typedef struct netField_s
|
|
{
|
|
const char *name; // field name
|
|
int32_t offset; // offset from the start of the entity struct
|
|
int32_t bits; // field size
|
|
|
|
} netField_t;
|
|
|
|
#define SECTF(x) #x,(int32_t)(size_t)&((netSector_t*)0)->x
|
|
|
|
static netField_t SectorFields[] =
|
|
{
|
|
{ SECTF(wallptr), 16 },
|
|
{ SECTF(wallnum), 16 },
|
|
|
|
{ SECTF(ceilingz), 32 },
|
|
{ SECTF(floorz), 32 },
|
|
|
|
{ SECTF(ceilingstat), 16 },
|
|
{ SECTF(floorstat), 16 },
|
|
{ SECTF(ceilingpicnum), 16 },
|
|
{ SECTF(ceilingheinum), 16 },
|
|
|
|
{ SECTF(ceilingshade), 8 },
|
|
{ SECTF(ceilingpal), 8 },
|
|
{ SECTF(ceilingxpanning), 8 },
|
|
{ SECTF(ceilingypanning), 8 },
|
|
|
|
{ SECTF(floorpicnum), 16 },
|
|
{ SECTF(floorheinum), 16 },
|
|
|
|
{ SECTF(floorshade), 8 },
|
|
{ SECTF(floorpal), 8 },
|
|
{ SECTF(floorxpanning), 8 },
|
|
{ SECTF(floorypanning), 8 },
|
|
{ SECTF(visibility), 8 },
|
|
{ SECTF(fogpal), 8 },
|
|
|
|
{ SECTF(lotag), 16 },
|
|
{ SECTF(hitag), 16 },
|
|
{ SECTF(extra), 16 },
|
|
|
|
};
|
|
|
|
#undef SECTF
|
|
|
|
|
|
#define WALLF(x) #x,(int32_t)(size_t)&((netWall_t*)0)->x
|
|
|
|
static netField_t WallFields[] =
|
|
{
|
|
{ WALLF(x), 32 },
|
|
{ WALLF(y), 32 },
|
|
|
|
{ WALLF(point2), 16 },
|
|
{ WALLF(nextwall), 16 },
|
|
{ WALLF(nextsector), 16 },
|
|
|
|
{ WALLF(cstat), 16 },
|
|
{ WALLF(picnum), 16 },
|
|
{ WALLF(overpicnum), 16 },
|
|
|
|
{ WALLF(shade), 8 },
|
|
{ WALLF(pal), 8 },
|
|
{ WALLF(xrepeat), 8 },
|
|
{ WALLF(yrepeat), 8 },
|
|
{ WALLF(xpanning), 8 },
|
|
{ WALLF(ypanning), 8 },
|
|
|
|
{ WALLF(lotag), 16 },
|
|
{ WALLF(hitag), 16 },
|
|
{ WALLF(extra), 16 },
|
|
|
|
};
|
|
|
|
#undef WALLF
|
|
|
|
|
|
#define ACTF(x) #x,(int32_t)(size_t)&((netactor_t*)0)->x
|
|
|
|
static netField_t ActorFields[] =
|
|
{
|
|
{ ACTF(t_data_0), 32 },
|
|
{ ACTF(t_data_1), 32 },
|
|
{ ACTF(t_data_2), 32 },
|
|
{ ACTF(t_data_3), 32 },
|
|
{ ACTF(t_data_4), 32 },
|
|
{ ACTF(t_data_5), 32 },
|
|
{ ACTF(t_data_6), 32 },
|
|
{ ACTF(t_data_7), 32 },
|
|
{ ACTF(t_data_8), 32 },
|
|
{ ACTF(t_data_9), 32 },
|
|
|
|
#ifdef LUNATIC
|
|
// need to update this section if LUNATIC is ever brought back in
|
|
{ ACTF(hvel), 16 },
|
|
{ ACTF(vvel), 16 },
|
|
|
|
|
|
{ ACTF(startframe), 16 },
|
|
{ ACTF(numframes), 16 },
|
|
|
|
{ ACTF(viewtype), 16 },
|
|
{ ACTF(incval), 16 },
|
|
{ ACTF(delay), 16 },
|
|
#endif
|
|
|
|
{ ACTF(flags), 32 },
|
|
|
|
{ ACTF(bpos_x), 32 },
|
|
{ ACTF(bpos_y), 32 },
|
|
{ ACTF(bpos_z), 32 },
|
|
|
|
{ ACTF(floorz), 32 },
|
|
{ ACTF(ceilingz), 32 },
|
|
{ ACTF(lastvx), 32 },
|
|
{ ACTF(lastvy), 32 },
|
|
|
|
{ ACTF(lasttransport), 8},
|
|
|
|
{ ACTF(picnum), 16 },
|
|
{ ACTF(ang), 16 },
|
|
{ ACTF(extra), 16 },
|
|
{ ACTF(owner), 16 },
|
|
|
|
{ ACTF(movflag), 16 },
|
|
{ ACTF(tempang), 16 },
|
|
{ ACTF(timetosleep), 16 },
|
|
|
|
{ ACTF(stayput), 16 },
|
|
{ ACTF(dispicnum), 16 },
|
|
|
|
#if defined LUNATIC
|
|
{ ACTF(movflags), 16 },
|
|
#endif
|
|
|
|
{ ACTF(cgg), 8},
|
|
|
|
//------------------------------------------------------
|
|
// sprite fields
|
|
|
|
{ ACTF(spr_x), 32 },
|
|
{ ACTF(spr_y), 32 },
|
|
{ ACTF(spr_z), 32 },
|
|
|
|
{ ACTF(spr_cstat), 16 },
|
|
|
|
{ ACTF(spr_picnum), 16 },
|
|
{ ACTF(spr_shade), 8 },
|
|
{ ACTF(spr_pal), 8 },
|
|
{ ACTF(spr_clipdist), 8 },
|
|
{ ACTF(spr_blend), 8 },
|
|
{ ACTF(spr_xrepeat), 8 },
|
|
{ ACTF(spr_yrepeat), 8 },
|
|
{ ACTF(spr_xoffset), 8 },
|
|
{ ACTF(spr_yoffset), 8 },
|
|
|
|
{ ACTF(spr_sectnum), 16 },
|
|
{ ACTF(spr_statnum), 16 },
|
|
|
|
{ ACTF(spr_ang), 16 },
|
|
{ ACTF(spr_owner), 16 },
|
|
{ ACTF(spr_xvel), 16 },
|
|
{ ACTF(spr_yvel), 16 },
|
|
{ ACTF(spr_zvel), 16 },
|
|
|
|
{ ACTF(spr_lotag), 16 },
|
|
{ ACTF(spr_hitag), 16 },
|
|
|
|
{ ACTF(spr_extra), 16 },
|
|
|
|
//--------------------------------------------------------------
|
|
//spriteext fields
|
|
|
|
{ ACTF(ext_mdanimtims), 32 },
|
|
{ ACTF(ext_mdanimcur), 16 },
|
|
{ ACTF(ext_angoff), 16 },
|
|
{ ACTF(ext_pitch), 16 },
|
|
{ ACTF(ext_roll), 16 },
|
|
|
|
{ ACTF(ext_offset_x), 32 },
|
|
{ ACTF(ext_offset_y), 32 },
|
|
{ ACTF(ext_offset_z), 32 },
|
|
|
|
{ ACTF(ext_flags), 8 },
|
|
{ ACTF(ext_xpanning), 8 },
|
|
{ ACTF(ext_ypanning), 8 },
|
|
{ ACTF(ext_alpha), 0 }, // float
|
|
|
|
//--------------------------------------------------------------
|
|
//spritesmooth fields
|
|
|
|
{ ACTF(sm_smoothduration), 0 }, // float
|
|
{ ACTF(sm_mdcurframe), 16 },
|
|
{ ACTF(sm_mdoldframe), 16 },
|
|
{ ACTF(sm_mdsmooth), 16 },
|
|
|
|
|
|
|
|
};
|
|
#undef ACTF
|
|
|
|
// actual game struct data size
|
|
#define WORLD_DATASIZE (MAXSECTORS * sizeof(netSector_t) + MAXWALLS * sizeof(netWall_t) + MAXSPRITES * sizeof(netactor_t))
|
|
|
|
// max packet array size
|
|
#define MAX_WORLDBUFFER WORLD_DATASIZE + WORLD_OVERHEADSIZE
|
|
|
|
#ifdef CURRENTLY_UNUSED
|
|
// Just so you can get an idea of how much memory the netcode needs...
|
|
static const int64_t cWORLD_DataSize = WORLD_DATASIZE;
|
|
static const int64_t cWORLD_OverheadSize = WORLD_OVERHEADSIZE;
|
|
static const int64_t cWORLD_TotalSize = MAX_WORLDBUFFER;
|
|
|
|
// ...it's pretty big for now (!)
|
|
static const int64_t SnapshotArraySize = sizeof(netmapstate_t) * NET_REVISIONS;
|
|
#endif
|
|
|
|
|
|
// both the client and server store their current revision number here,
|
|
// when the client sends their revision to the server,
|
|
// the server stores it in g_player[that player's index].revision
|
|
static uint32_t g_netMapRevisionNumber = 0;
|
|
|
|
// In addition to the client keeping track of what revision the server is using
|
|
// (g_netRevisionNumber), it also increments its own revision that represents
|
|
// what version of the game the client has interpolated to.
|
|
static uint32_t g_cl_InterpolatedRevision = 0;
|
|
|
|
static netmapstate_t g_mapStartState;
|
|
|
|
static netmapstate_t g_cl_InterpolatedMapStateHistory[NET_REVISIONS];
|
|
|
|
// note that the map state number is not an index into here,
|
|
// to get the index into this array out of a map state number, do <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 = §or[index];
|
|
netSector_t* snapshotSector = &snapshot->sector[index];
|
|
|
|
Net_CopySectorToNet(gameSector, snapshotSector, index);
|
|
|
|
}
|
|
|
|
Net_AddActorsToSnapshot(snapshot);
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
static void P_RemovePlayer(int32_t p)
|
|
{
|
|
// server obviously can't leave the game, and index 0 shows up for disconnect events from
|
|
// players that haven't gotten far enough into the connection process to get a player ID
|
|
|
|
if (p <= 0) return;
|
|
|
|
g_player[p].playerquitflag = 0;
|
|
|
|
Bsprintf(recbuf,"%s^00 is history!",g_player[p].user_name);
|
|
G_AddUserQuote(recbuf);
|
|
|
|
if (numplayers == 1)
|
|
S_PlaySound(GENERIC_AMBIENCE17);
|
|
|
|
if (g_player[myconnectindex].ps->gm & MODE_GAME)
|
|
{
|
|
if (screenpeek == p)
|
|
screenpeek = myconnectindex;
|
|
|
|
pub = NUMPAGES;
|
|
pus = NUMPAGES;
|
|
G_UpdateScreenArea();
|
|
|
|
P_QuickKill(g_player[p].ps);
|
|
|
|
if (voting == p)
|
|
{
|
|
for (p=0; 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 = (int32_t) totalclock;
|
|
g_player[newPlayerIndex].playerquitflag = 1;
|
|
|
|
NET_75_CHECK++; // Need to think of something better when finding a remaining slot for players.
|
|
|
|
for (j = 0; j < g_mostConcurrentPlayers - 1; j++)
|
|
{
|
|
connectpoint2[j] = j + 1;
|
|
}
|
|
|
|
connectpoint2[g_mostConcurrentPlayers - 1] = -1;
|
|
|
|
G_MaybeAllocPlayer(newPlayerIndex);
|
|
|
|
g_netPlayersWaiting--;
|
|
++numplayers;
|
|
++ud.multimode;
|
|
Net_SendNewPlayer(newPlayerIndex);
|
|
Net_SendPlayerIndex(newPlayerIndex, event->peer);
|
|
Net_SendClientInfo();
|
|
Net_SendUserMapName();
|
|
Net_SendNewGame(0, event->peer); // newly connecting player (Net_SyncPlayer)
|
|
}
|
|
|
|
|
|
|
|
static void display_betascreen(void)
|
|
{
|
|
rotatesprite_fs(160 << 16, 100 << 16, 65536, 0, BETASCREEN, 0, 0, 2 + 8 + 64 + BGSTRETCH);
|
|
|
|
rotatesprite_fs(160 << 16, (104) << 16, 60 << 10, 0, DUKENUKEM, 0, 0, 2 + 8);
|
|
rotatesprite_fs(160 << 16, (129) << 16, 30 << 11, 0, THREEDEE, 0, 0, 2 + 8);
|
|
if (PLUTOPAK) // JBF 20030804
|
|
rotatesprite_fs(160 << 16, (151) << 16, 30 << 11, 0, PLUTOPAKSPRITE + 1, 0, 0, 2 + 8);
|
|
}
|
|
|
|
|
|
static void Net_Disconnect(void)
|
|
{
|
|
if (g_netClient)
|
|
{
|
|
ENetEvent event;
|
|
|
|
if (g_netClientPeer)
|
|
enet_peer_disconnect_later(g_netClientPeer, 0);
|
|
|
|
while (enet_host_service(g_netClient, &event, 3000) > 0)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case ENET_EVENT_TYPE_CONNECT:
|
|
case ENET_EVENT_TYPE_NONE:
|
|
case ENET_EVENT_TYPE_RECEIVE:
|
|
if (event.packet)
|
|
enet_packet_destroy(event.packet);
|
|
break;
|
|
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
numplayers = g_mostConcurrentPlayers = ud.multimode = 1;
|
|
myconnectindex = screenpeek = 0;
|
|
G_BackToMenu();
|
|
break;
|
|
}
|
|
}
|
|
|
|
enet_peer_reset(g_netClientPeer);
|
|
g_netClientPeer = NULL;
|
|
enet_host_destroy(g_netClient);
|
|
g_netClient = NULL;
|
|
return;
|
|
}
|
|
|
|
if (g_netServer)
|
|
{
|
|
int32_t peerIndex;
|
|
ENetEvent event;
|
|
|
|
for (peerIndex = 0; peerIndex < (signed)g_netServer->peerCount; peerIndex++)
|
|
{
|
|
enet_peer_disconnect_later(&g_netServer->peers[peerIndex], DISC_SERVER_QUIT);
|
|
}
|
|
while (enet_host_service(g_netServer, &event, 3000) > 0)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case ENET_EVENT_TYPE_CONNECT:
|
|
case ENET_EVENT_TYPE_NONE:
|
|
case ENET_EVENT_TYPE_RECEIVE:
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
if (event.packet)
|
|
{
|
|
enet_packet_destroy(event.packet);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
enet_host_destroy(g_netServer);
|
|
g_netServer = NULL;
|
|
}
|
|
}
|
|
|
|
// Only clients run this
|
|
static void Net_ReceiveDisconnect(ENetEvent *event)
|
|
{
|
|
g_netDisconnect = 1;
|
|
numplayers = g_mostConcurrentPlayers = ud.multimode = 1;
|
|
myconnectindex = screenpeek = 0;
|
|
G_BackToMenu();
|
|
|
|
switch (event->data)
|
|
{
|
|
case DISC_BAD_PASSWORD:
|
|
initprintf("Bad password.\n");
|
|
return;
|
|
case DISC_VERSION_MISMATCH:
|
|
initprintf("Version mismatch.\n");
|
|
return;
|
|
case DISC_INVALID:
|
|
initprintf("Invalid data detected.\n");
|
|
return;
|
|
case DISC_SERVER_QUIT:
|
|
initprintf("The server is quitting.\n");
|
|
return;
|
|
case DISC_SERVER_FULL:
|
|
initprintf("The server is full.\n");
|
|
return;
|
|
case DISC_KICKED:
|
|
initprintf("You have been kicked from the server.\n");
|
|
return;
|
|
case DISC_BANNED:
|
|
initprintf("You are banned from this server.\n");
|
|
return;
|
|
default:
|
|
initprintf("Disconnected.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void Net_SendAcknowledge(ENetPeer *client)
|
|
{
|
|
if (!g_netServer)
|
|
return;
|
|
|
|
tempnetbuf[0] = PACKET_ACK;
|
|
tempnetbuf[1] = myconnectindex;
|
|
|
|
enet_peer_send(client, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
|
|
|
|
Dbg_PacketSent(PACKET_ACK);
|
|
}
|
|
|
|
static void Net_ExtractPlayerUpdate(playerupdate_t *update, int32_t type)
|
|
{
|
|
const int32_t playerindex = update->playerindex;
|
|
|
|
if (playerindex != myconnectindex)
|
|
{
|
|
g_player[playerindex].ps->pos = update->pos;
|
|
g_player[playerindex].ps->opos = update->opos;
|
|
g_player[playerindex].ps->vel = update->vel;
|
|
g_player[playerindex].ps->q16ang = update->q16ang;
|
|
g_player[playerindex].ps->q16horiz = update->q16horiz;
|
|
g_player[playerindex].ps->q16horizoff = update->q16horizoff;
|
|
}
|
|
|
|
if (type == PACKET_MASTER_TO_SLAVE)
|
|
{
|
|
g_player[playerindex].ping = update->ping;
|
|
g_player[playerindex].ps->dead_flag = update->deadflag;
|
|
g_player[playerindex].playerquitflag = update->playerquitflag;
|
|
}
|
|
|
|
}
|
|
|
|
// Server only
|
|
static void Net_ReceiveClientUpdate(ENetEvent *event)
|
|
{
|
|
int32_t playeridx;
|
|
clientupdate_t update;
|
|
|
|
if (event->packet->dataLength != sizeof(clientupdate_t))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Bmemcpy(&update, (char *)event->packet->data, sizeof(clientupdate_t));
|
|
|
|
playeridx = (int32_t)(intptr_t)event->peer->data;
|
|
|
|
if (playeridx < 0 || playeridx >= MAXPLAYERS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_player[playeridx].revision = update.RevisionNumber;
|
|
inputfifo[0][playeridx] = update.nsyn;
|
|
|
|
Net_ExtractPlayerUpdate(&update.player, PACKET_SLAVE_TO_MASTER);
|
|
}
|
|
|
|
static void Net_Server_SetupPlayer(int playerindex)
|
|
{
|
|
int16_t playerspriteindex;
|
|
|
|
playerspriteindex = g_player[playerindex].ps->i;
|
|
Bmemcpy(g_player[playerindex].ps, g_player[0].ps, sizeof(DukePlayer_t));
|
|
|
|
g_player[playerindex].ps->i = playerspriteindex;
|
|
changespritestat(playerspriteindex, STAT_PLAYER);
|
|
|
|
g_player[playerindex].ps->last_extra = sprite[g_player[playerindex].ps->i].extra = g_player[playerindex].ps->max_player_health;
|
|
sprite[g_player[playerindex].ps->i].cstat = 1 + 256;
|
|
actor[g_player[playerindex].ps->i].t_data[2] = actor[g_player[playerindex].ps->i].t_data[3]
|
|
= actor[g_player[playerindex].ps->i].t_data[4] = 0;
|
|
|
|
P_ResetPlayer(playerindex);
|
|
Net_SpawnPlayer(playerindex);
|
|
}
|
|
|
|
static void Net_ReceiveChallenge(uint8_t *pbuf, int32_t packbufleng, ENetEvent *event)
|
|
{
|
|
const uint16_t byteVersion = B_UNBUF16(&pbuf[1]);
|
|
const uint16_t netVersion = B_UNBUF16(&pbuf[3]);
|
|
const uint32_t crc = B_UNBUF32(&pbuf[5]);
|
|
|
|
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
|
|
|
|
if (byteVersion != BYTEVERSION || netVersion != NETVERSION)
|
|
{
|
|
enet_peer_disconnect_later(event->peer, DISC_VERSION_MISMATCH);
|
|
initprintf("Bad client protocol: version %u.%u\n", byteVersion, netVersion);
|
|
return;
|
|
}
|
|
if (crc != Bcrc32((uint8_t *)g_netPassword, Bstrlen(g_netPassword), 0))
|
|
{
|
|
enet_peer_disconnect_later(event->peer, DISC_BAD_PASSWORD);
|
|
initprintf("Bad password from client.\n");
|
|
return;
|
|
}
|
|
|
|
Net_SyncPlayer(event);
|
|
}
|
|
|
|
static void Net_ReceiveMessage(uint8_t *pbuf, int32_t packbufleng)
|
|
{
|
|
Bstrncpy(recbuf, (char *)pbuf + 2, packbufleng - 2);
|
|
recbuf[packbufleng - 2] = 0;
|
|
|
|
G_AddUserQuote(recbuf);
|
|
S_PlaySound(EXITMENUSOUND);
|
|
|
|
pus = pub = NUMPAGES;
|
|
}
|
|
|
|
|
|
static void Net_CheckForEnoughVotes()
|
|
{
|
|
|
|
|
|
|
|
// Only the server can decide map changes
|
|
if (!g_netServer || numplayers <= 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32_t requiredvotes;
|
|
|
|
// If there are just two players, both of them deserve a vote
|
|
if (numplayers == 2)
|
|
{
|
|
requiredvotes = 2;
|
|
}
|
|
else
|
|
{
|
|
// If more than two players, we need at least 50% of the players to vote
|
|
// Which means that if there's an odd number of players, we'll need slightly more than 50% of the vote.
|
|
requiredvotes = numplayers / 2;
|
|
if (numplayers % 2 == 1)
|
|
{
|
|
requiredvotes++;
|
|
}
|
|
}
|
|
|
|
int32_t numfor = 0;
|
|
int32_t numagainst = 0;
|
|
|
|
for (int32_t playerIndex = 0; playerIndex < MAXPLAYERS; playerIndex++)
|
|
{
|
|
if (g_player[playerIndex].gotvote)
|
|
{
|
|
if (g_player[playerIndex].vote)
|
|
{
|
|
numfor++;
|
|
}
|
|
else
|
|
{
|
|
numagainst++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numfor >= requiredvotes)
|
|
{
|
|
Net_StartNewGame();
|
|
Net_SendNewGame(1, NULL); // map vote
|
|
}
|
|
else if (numagainst >= requiredvotes || (numfor + numagainst) == numplayers)
|
|
{
|
|
Net_SendMapVoteCancel(1);
|
|
}
|
|
}
|
|
|
|
|
|
static void Net_ReceiveMapVote(uint8_t *pbuf)
|
|
{
|
|
if (voting == myconnectindex && g_player[(uint8_t)pbuf[1]].gotvote == 0)
|
|
{
|
|
Bsprintf(tempbuf, "Confirmed vote from %s", g_player[(uint8_t)pbuf[1]].user_name);
|
|
G_AddUserQuote(tempbuf);
|
|
}
|
|
|
|
if (!g_netServer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_player[(uint8_t)pbuf[1]].gotvote = 1;
|
|
g_player[(uint8_t)pbuf[1]].vote = pbuf[2];
|
|
Net_CheckForEnoughVotes();
|
|
}
|
|
|
|
|
|
static void Net_ReceiveMapVoteCancel(uint8_t *pbuf)
|
|
{
|
|
// Ignore if we're not voting
|
|
if (voting == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Ignore cancellations from clients that did not initiate the map vote
|
|
if (voting != pbuf[1] && voting != myconnectindex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (voting == myconnectindex || voting != pbuf[1])
|
|
{
|
|
Bsprintf(tempbuf, "Vote Failed");
|
|
}
|
|
else if (voting == pbuf[1])
|
|
{
|
|
Bsprintf(tempbuf, "%s^00 has canceled the vote", g_player[voting].user_name);
|
|
}
|
|
|
|
G_AddUserQuote(tempbuf);
|
|
|
|
if (g_netServer)
|
|
{
|
|
Net_SendMapVoteCancel(0);
|
|
}
|
|
|
|
voting = -1;
|
|
}
|
|
|
|
static void Net_ReceiveClientInfo(uint8_t *pbuf, int32_t packbufleng, int32_t fromserver)
|
|
{
|
|
uint32_t byteIndex, j;
|
|
int32_t other = pbuf[packbufleng];
|
|
|
|
for (byteIndex = 1; pbuf[byteIndex]; byteIndex++)
|
|
{
|
|
g_player[other].user_name[byteIndex - 1] = pbuf[byteIndex];
|
|
}
|
|
|
|
g_player[other].user_name[byteIndex - 1] = 0;
|
|
byteIndex++;
|
|
|
|
g_player[other].ps->aim_mode = pbuf[byteIndex++];
|
|
g_player[other].ps->auto_aim = pbuf[byteIndex++];
|
|
g_player[other].ps->weaponswitch = pbuf[byteIndex++];
|
|
g_player[other].ps->palookup = g_player[other].pcolor = pbuf[byteIndex++];
|
|
g_player[other].pteam = pbuf[byteIndex++];
|
|
|
|
for (j = byteIndex; byteIndex - j < 10; byteIndex++)
|
|
{
|
|
g_player[other].wchoice[byteIndex - j] = pbuf[byteIndex];
|
|
}
|
|
|
|
if (fromserver)
|
|
{
|
|
g_player[other].playerquitflag = 1;
|
|
}
|
|
}
|
|
|
|
static void Net_ReceiveUserMapName(uint8_t *pbuf, int32_t packbufleng)
|
|
{
|
|
NET_75_CHECK++; // possible buffer bug here with a malicious client? if the file name didn't end in '\0', a hacker could "forget" to put '\0'
|
|
// at the end.
|
|
|
|
Bstrcpy(boardfilename, (char *)pbuf + 1);
|
|
boardfilename[packbufleng - 1] = 0;
|
|
Bcorrectfilename(boardfilename, 0);
|
|
if (boardfilename[0] != 0)
|
|
{
|
|
buildvfs_kfd i;
|
|
if ((i = kopen4loadfrommod(boardfilename, 0)) == buildvfs_kfd_invalid)
|
|
{
|
|
Bmemset(boardfilename, 0, sizeof(boardfilename));
|
|
Net_SendUserMapName();
|
|
}
|
|
else
|
|
{
|
|
kclose(i);
|
|
}
|
|
}
|
|
|
|
if (ud.m_level_number == 7 && ud.m_volume_number == 0 && boardfilename[0] == 0)
|
|
ud.m_level_number = 0;
|
|
}
|
|
|
|
static void Net_ExtractNewGame(newgame_t *newgame, int32_t menuonly)
|
|
{
|
|
ud.m_level_number = newgame->level_number;
|
|
ud.m_volume_number = newgame->volume_number;
|
|
ud.m_player_skill = newgame->player_skill;
|
|
ud.m_monsters_off = newgame->monsters_off;
|
|
ud.m_respawn_monsters = newgame->respawn_monsters;
|
|
ud.m_respawn_items = newgame->respawn_items;
|
|
ud.m_respawn_inventory = newgame->respawn_inventory;
|
|
ud.m_ffire = newgame->ffire;
|
|
ud.m_noexits = newgame->noexits;
|
|
ud.m_coop = newgame->coop;
|
|
|
|
if (!menuonly)
|
|
{
|
|
ud.level_number = newgame->level_number;
|
|
ud.volume_number = newgame->volume_number;
|
|
ud.player_skill = newgame->player_skill;
|
|
ud.monsters_off = newgame->monsters_off;
|
|
ud.respawn_monsters = newgame->respawn_monsters;
|
|
ud.respawn_monsters = newgame->respawn_items;
|
|
ud.respawn_inventory = newgame->respawn_inventory;
|
|
ud.ffire = newgame->ffire;
|
|
ud.noexits = newgame->noexits;
|
|
ud.coop = newgame->coop;
|
|
}
|
|
}
|
|
|
|
static void Net_ReceiveMapVoteInitiate(uint8_t *pbuf)
|
|
{
|
|
int32_t playerIndex;
|
|
|
|
Bmemcpy(&pendingnewgame, pbuf, sizeof(newgame_t));
|
|
Net_ExtractNewGame(&pendingnewgame, 1);
|
|
|
|
voting = pendingnewgame.connection;
|
|
vote_episode = pendingnewgame.volume_number;
|
|
vote_map = pendingnewgame.level_number;
|
|
|
|
Bsprintf(tempbuf, "%s^00 has called a vote to change map to %s (E%dL%d)", g_player[voting].user_name,
|
|
g_mapInfo[(uint8_t)(vote_episode * MAXLEVELS + vote_map)].name, vote_episode + 1, vote_map + 1);
|
|
G_AddUserQuote(tempbuf);
|
|
|
|
Bsprintf(tempbuf, "Press F1 to Accept, F2 to Decline");
|
|
G_AddUserQuote(tempbuf);
|
|
|
|
for (playerIndex = MAXPLAYERS - 1; playerIndex >= 0; playerIndex--)
|
|
{
|
|
g_player[playerIndex].vote = 0;
|
|
g_player[playerIndex].gotvote = 0;
|
|
}
|
|
|
|
g_player[voting].gotvote = g_player[voting].vote = 1;
|
|
}
|
|
|
|
|
|
static void Net_ParsePacketCommon(uint8_t *pbuf, int32_t packbufleng, int32_t serverpacketp)
|
|
{
|
|
switch (pbuf[0])
|
|
{
|
|
case PACKET_MESSAGE:
|
|
Net_ReceiveMessage(pbuf, packbufleng);
|
|
break;
|
|
|
|
case PACKET_CLIENT_INFO:
|
|
Net_ReceiveClientInfo(pbuf, packbufleng, serverpacketp);
|
|
break;
|
|
|
|
case PACKET_RTS:
|
|
G_StartRTS(pbuf[1], 0);
|
|
break;
|
|
|
|
case PACKET_USER_MAP:
|
|
Net_ReceiveUserMapName(pbuf, packbufleng);
|
|
break;
|
|
|
|
case PACKET_MAP_VOTE:
|
|
Net_ReceiveMapVote(pbuf);
|
|
break;
|
|
|
|
case PACKET_MAP_VOTE_INITIATE: // call map vote
|
|
Net_ReceiveMapVoteInitiate(pbuf);
|
|
break;
|
|
|
|
case PACKET_MAP_VOTE_CANCEL: // cancel map vote
|
|
Net_ReceiveMapVoteCancel(pbuf);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void Net_ParseClientPacket(ENetEvent *event)
|
|
{
|
|
uint8_t *pbuf = event->packet->data;
|
|
int32_t packbufleng = event->packet->dataLength;
|
|
|
|
int32_t other = pbuf[--packbufleng];
|
|
|
|
NET_DEBUG_VAR enum DukePacket_t packetType = (enum DukePacket_t)pbuf[0];
|
|
|
|
#ifdef PACKET_RECV_PRINT
|
|
initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng);
|
|
#endif
|
|
switch (pbuf[0])
|
|
{
|
|
case PACKET_SLAVE_TO_MASTER: //[1] (receive slave sync buffer)
|
|
Net_ReceiveClientUpdate(event);
|
|
break;
|
|
|
|
case PACKET_PLAYER_READY:
|
|
{
|
|
if ((other == 0) || (other == myconnectindex) || (other >= MAXPLAYERS))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// At intermission, just set the ready flag and wait for the other players.
|
|
if (g_player[myconnectindex].ps->gm & MODE_EOL)
|
|
{
|
|
g_player[other].ready = 1;
|
|
}
|
|
else
|
|
{
|
|
// otherwise if the server's in a map now, set up the player immediately.
|
|
Net_Server_SetupPlayer(other);
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
case PACKET_PLAYER_PING:
|
|
if (g_player[myconnectindex].ps->gm & MODE_GAME)
|
|
{
|
|
packbuf[0] = PACKET_PLAYER_PING;
|
|
packbuf[1] = myconnectindex;
|
|
enet_peer_send(event->peer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
|
|
|
|
Dbg_PacketSent(PACKET_PLAYER_PING);
|
|
}
|
|
g_player[other].pingcnt++;
|
|
break;
|
|
|
|
case PACKET_AUTH:
|
|
Net_ReceiveChallenge(pbuf, packbufleng, event);
|
|
break;
|
|
|
|
default:
|
|
Net_ParsePacketCommon(pbuf, packbufleng, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void Net_HandleClientPackets(void)
|
|
{
|
|
ENetEvent event;
|
|
|
|
// pull events from the wire into the packet queue without dispatching them, once per Net_GetPackets() call
|
|
enet_host_service(g_netServer, NULL, 0);
|
|
|
|
// dispatch any pending events from the local packet queue
|
|
while (enet_host_check_events(g_netServer, &event) > 0)
|
|
{
|
|
const intptr_t playeridx = (intptr_t)event.peer->data;
|
|
|
|
if (playeridx < 0 || playeridx >= MAXPLAYERS)
|
|
{
|
|
enet_peer_disconnect_later(event.peer, DISC_INVALID);
|
|
buildprint("Invalid player id (", playeridx, ") from client.\n");
|
|
continue;
|
|
}
|
|
|
|
switch (event.type)
|
|
{
|
|
case ENET_EVENT_TYPE_CONNECT:
|
|
{
|
|
char ipaddr[32];
|
|
|
|
enet_address_get_host_ip(&event.peer->address, ipaddr, sizeof(ipaddr));
|
|
|
|
OSD_Printf("A new client connected from %s:%u.\n", ipaddr, event.peer->address.port);
|
|
|
|
//[75] Temporary: For now the netcode can't handle more players connecting than there are player starts.
|
|
if (g_playerSpawnCnt <= ud.multimode)
|
|
{
|
|
OSD_Printf("Connection dropped: No player spawn point available for this new player.");
|
|
break;
|
|
}
|
|
|
|
Net_SendAcknowledge(event.peer);
|
|
break;
|
|
}
|
|
|
|
case ENET_EVENT_TYPE_RECEIVE:
|
|
Net_ParseClientPacket(&event);
|
|
// broadcast takes care of enet_packet_destroy itself
|
|
// we set the state to disconnected so enet_host_broadcast
|
|
// doesn't send the player back his own packets
|
|
if ((event.channelID == CHAN_GAMESTATE && event.packet->data[0] > PACKET_BROADCAST)
|
|
|| event.channelID == CHAN_CHAT)
|
|
{
|
|
const ENetPacket *pak = event.packet;
|
|
|
|
event.peer->state = ENET_PEER_STATE_DISCONNECTED;
|
|
enet_host_broadcast(g_netServer, event.channelID,
|
|
enet_packet_create(pak->data, pak->dataLength, pak->flags&ENET_PACKET_FLAG_RELIABLE));
|
|
event.peer->state = ENET_PEER_STATE_CONNECTED;
|
|
}
|
|
|
|
enet_packet_destroy(event.packet);
|
|
g_player[playeridx].ping = (event.peer->lastRoundTripTime + event.peer->roundTripTime) / 2;
|
|
break;
|
|
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
numplayers--;
|
|
ud.multimode--;
|
|
|
|
P_RemovePlayer(playeridx);
|
|
|
|
g_player[playeridx].revision = cInitialMapStateRevisionNumber;
|
|
|
|
packbuf[0] = PACKET_PLAYER_DISCONNECTED;
|
|
packbuf[1] = playeridx;
|
|
packbuf[2] = numplayers;
|
|
packbuf[3] = ud.multimode;
|
|
packbuf[4] = g_mostConcurrentPlayers;
|
|
packbuf[5] = myconnectindex;
|
|
|
|
enet_host_broadcast(g_netServer, CHAN_GAMESTATE,
|
|
enet_packet_create(&packbuf[0], 6, ENET_PACKET_FLAG_RELIABLE));
|
|
|
|
initprintf("%s disconnected.\n", g_player[playeridx].user_name);
|
|
event.peer->data = NULL;
|
|
|
|
Dbg_PacketSent(PACKET_PLAYER_DISCONNECTED);
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void Net_ReceiveServerUpdate(ENetEvent *event)
|
|
{
|
|
serverupdate_t serverupdate;
|
|
serverplayerupdate_t playerupdate;
|
|
|
|
if (((event->packet->dataLength - sizeof(serverupdate_t)) % sizeof(serverplayerupdate_t)) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint8_t *updatebuf = (uint8_t *)event->packet->data;
|
|
Bmemcpy(&serverupdate, updatebuf, sizeof(serverupdate_t));
|
|
updatebuf += sizeof(serverupdate_t);
|
|
inputfifo[0][0] = serverupdate.nsyn;
|
|
ud.pause_on = serverupdate.pause_on;
|
|
|
|
ticrandomseed = serverupdate.seed;
|
|
|
|
for (int playerIndex = 0; playerIndex < serverupdate.numplayers; ++playerIndex)
|
|
{
|
|
Bmemcpy(&playerupdate, updatebuf, sizeof(serverplayerupdate_t));
|
|
updatebuf += sizeof(serverplayerupdate_t);
|
|
|
|
Net_ExtractPlayerUpdate(&playerupdate.player, PACKET_MASTER_TO_SLAVE);
|
|
|
|
g_player[playerIndex].ps->gotweapon = playerupdate.gotweapon;
|
|
|
|
g_player[playerIndex].ps->kickback_pic = playerupdate.kickback_pic;
|
|
|
|
Bmemcpy(g_player[playerIndex].frags, playerupdate.frags, sizeof(playerupdate.frags));
|
|
Bmemcpy(g_player[playerIndex].ps->inv_amount, playerupdate.inv_amount, sizeof(playerupdate.inv_amount));
|
|
Bmemcpy(g_player[playerIndex].ps->ammo_amount, playerupdate.ammo_amount, sizeof(playerupdate.ammo_amount));
|
|
|
|
g_player[playerIndex].ps->curr_weapon = playerupdate.curr_weapon;
|
|
g_player[playerIndex].ps->last_weapon = playerupdate.last_weapon;
|
|
g_player[playerIndex].ps->wantweaponfire = playerupdate.wantweaponfire;
|
|
g_player[playerIndex].ps->weapon_pos = playerupdate.weapon_pos;
|
|
g_player[playerIndex].ps->frag_ps = playerupdate.frag_ps;
|
|
g_player[playerIndex].ps->frag = playerupdate.frag;
|
|
g_player[playerIndex].ps->fraggedself = playerupdate.fraggedself;
|
|
g_player[playerIndex].ps->last_extra = playerupdate.last_extra;
|
|
g_player[playerIndex].ping = playerupdate.ping;
|
|
|
|
|
|
g_player[playerIndex].ps->newowner = playerupdate.newowner;
|
|
}
|
|
}
|
|
|
|
// sends the version and a simple crc32 of the current password, all verified by the server before the connection can continue
|
|
static void Net_SendChallenge()
|
|
{
|
|
if (!g_netClientPeer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
tempnetbuf[0] = PACKET_AUTH;
|
|
B_BUF16(&tempnetbuf[1], BYTEVERSION);
|
|
B_BUF16(&tempnetbuf[3], NETVERSION);
|
|
B_BUF32(&tempnetbuf[5], Bcrc32((uint8_t *)g_netPassword, Bstrlen(g_netPassword), 0));
|
|
tempnetbuf[9] = myconnectindex;
|
|
|
|
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&tempnetbuf[0], 10, ENET_PACKET_FLAG_RELIABLE));
|
|
|
|
Dbg_PacketSent(PACKET_AUTH);
|
|
}
|
|
|
|
|
|
static void Net_ReceiveAcknowledge(uint8_t *pbuf, int32_t packbufleng)
|
|
{
|
|
UNREFERENCED_PARAMETER(pbuf); // remove when this variable is used
|
|
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
|
|
|
|
Net_SendChallenge();
|
|
}
|
|
|
|
|
|
// client only
|
|
static void Net_ReceiveNewGame(ENetEvent *event)
|
|
{
|
|
ClientPlayerReady = 0;
|
|
|
|
if ((vote_map + vote_episode + voting) != -3)
|
|
G_AddUserQuote("Vote Succeeded");
|
|
|
|
Bmemcpy(&pendingnewgame, event->packet->data, sizeof(newgame_t));
|
|
Net_StartNewGame();
|
|
|
|
packbuf[0] = PACKET_PLAYER_READY;
|
|
packbuf[1] = myconnectindex;
|
|
|
|
if (g_netClientPeer)
|
|
{
|
|
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
|
|
Dbg_PacketSent(PACKET_PLAYER_READY);
|
|
|
|
if (g_netClient)
|
|
{
|
|
NET_75_CHECK++; // are these extra calls to create initial snapshots necessary or is it sufficient to just have it in
|
|
// G_EnterLevel?
|
|
/*
|
|
|
|
Net_InitMapStateHistory();
|
|
Net_AddWorldToInitialSnapshot();
|
|
|
|
*/
|
|
ClientPlayerReady = 1;
|
|
}
|
|
}
|
|
|
|
//[75] Note: DON'T set the initial map state or initialize the map state history in the packet code,
|
|
// The client didn't load the map until G_EnterLevel
|
|
|
|
g_player[myconnectindex].ps->gm = MODE_GAME;
|
|
ready2send = 1;
|
|
}
|
|
|
|
|
|
static void Net_ReceiveNewPlayer(uint8_t *pbuf, int32_t packbufleng)
|
|
{
|
|
int32_t i;
|
|
|
|
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
|
|
|
|
numplayers = pbuf[1];
|
|
g_mostConcurrentPlayers = pbuf[2];
|
|
ud.multimode = pbuf[3];
|
|
if (pbuf[4]) // ID of new player
|
|
{
|
|
g_player[pbuf[4]].playerquitflag = 1;
|
|
|
|
if (!g_player[pbuf[4]].ps)
|
|
{
|
|
g_player[pbuf[4]].ps = (DukePlayer_t *)Xcalloc(1, sizeof(DukePlayer_t));
|
|
}
|
|
if (!g_player[pbuf[4]].input)
|
|
{
|
|
g_player[pbuf[4]].input = (input_t *)Xcalloc(1, sizeof(input_t));
|
|
}
|
|
}
|
|
|
|
if (pbuf[5] == NET_DEDICATED_SERVER)
|
|
{
|
|
g_networkMode = NET_DEDICATED_CLIENT;
|
|
}
|
|
|
|
for (i = 0; i < g_mostConcurrentPlayers - 1; i++)
|
|
{
|
|
connectpoint2[i] = i + 1;
|
|
}
|
|
|
|
connectpoint2[g_mostConcurrentPlayers - 1] = -1;
|
|
|
|
S_PlaySound(DUKE_GETWEAPON2);
|
|
|
|
// myconnectindex is 0 until we get PACKET_PLAYER_INDEX
|
|
if (myconnectindex != 0)
|
|
{
|
|
Net_SendClientInfo();
|
|
}
|
|
}
|
|
|
|
static void Net_ReceivePlayerIndex(uint8_t *pbuf, int32_t packbufleng)
|
|
{
|
|
UNREFERENCED_PARAMETER(packbufleng); // remove when this variable is used
|
|
|
|
myconnectindex = pbuf[1];
|
|
g_player[myconnectindex].playerquitflag = 1;
|
|
Net_SendClientInfo();
|
|
}
|
|
|
|
|
|
static void Net_ParseServerPacket(ENetEvent *event)
|
|
{
|
|
uint8_t *pbuf = event->packet->data;
|
|
int32_t packbufleng = event->packet->dataLength;
|
|
// input_t *nsyn;
|
|
|
|
NET_DEBUG_VAR enum DukePacket_t packetType = (enum DukePacket_t) pbuf[0];
|
|
|
|
--packbufleng;
|
|
|
|
#ifdef PACKET_RECV_PRINT
|
|
initprintf("Received Packet: type: %d : len %d\n", pbuf[0], packbufleng);
|
|
#endif
|
|
switch (pbuf[0])
|
|
{
|
|
case PACKET_MASTER_TO_SLAVE:
|
|
|
|
if (!(g_player[myconnectindex].ps->gm & MODE_GAME))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Net_ReceiveServerUpdate(event);
|
|
break;
|
|
|
|
case PACKET_NEW_GAME:
|
|
Net_ReceiveNewGame(event);
|
|
break;
|
|
|
|
case PACKET_ACK:
|
|
Net_ReceiveAcknowledge(pbuf, packbufleng);
|
|
break;
|
|
|
|
case PACKET_NUM_PLAYERS:
|
|
Net_ReceiveNewPlayer(event->packet->data, event->packet->dataLength);
|
|
break;
|
|
|
|
case PACKET_PLAYER_INDEX:
|
|
Net_ReceivePlayerIndex(event->packet->data, event->packet->dataLength);
|
|
break;
|
|
|
|
case PACKET_PLAYER_DISCONNECTED:
|
|
if ((g_player[myconnectindex].ps->gm & MODE_GAME))
|
|
{
|
|
P_RemovePlayer(pbuf[1]);
|
|
}
|
|
|
|
numplayers = pbuf[2];
|
|
ud.multimode = pbuf[3];
|
|
g_mostConcurrentPlayers = pbuf[4];
|
|
|
|
break;
|
|
|
|
case PACKET_PLAYER_SPAWN:
|
|
if (!(g_player[myconnectindex].ps->gm & MODE_GAME))
|
|
{
|
|
break;
|
|
}
|
|
|
|
P_ResetPlayer(pbuf[1]);
|
|
Bmemcpy(&g_player[pbuf[1]].ps->pos.x, &pbuf[2], sizeof(vec3_t) * 2);
|
|
Bmemcpy(&sprite[g_player[pbuf[1]].ps->i], &pbuf[2], sizeof(vec3_t));
|
|
|
|
break;
|
|
|
|
case PACKET_PLAYER_PING:
|
|
g_player[0].pingcnt++;
|
|
return;
|
|
|
|
case PACKET_FRAG:
|
|
if (!(g_player[myconnectindex].ps->gm & MODE_GAME))
|
|
{
|
|
break;
|
|
}
|
|
|
|
g_player[pbuf[1]].ps->frag_ps = pbuf[2];
|
|
actor[g_player[pbuf[1]].ps->i].picnum = pbuf[3];
|
|
ticrandomseed = B_UNBUF32(&pbuf[4]);
|
|
P_FragPlayer(pbuf[1]);
|
|
|
|
break;
|
|
|
|
// [75]
|
|
case PACKET_WORLD_UPDATE:
|
|
Net_ReadWorldUpdate(pbuf, packbufleng);
|
|
break;
|
|
|
|
|
|
default:
|
|
Net_ParsePacketCommon(pbuf, packbufleng, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void Net_HandleServerPackets(void)
|
|
{
|
|
ENetEvent event;
|
|
|
|
enet_host_service(g_netClient, NULL, 0);
|
|
|
|
while (enet_host_check_events(g_netClient, &event) > 0)
|
|
{
|
|
if (event.type == ENET_EVENT_TYPE_DISCONNECT)
|
|
{
|
|
Net_ReceiveDisconnect(&event);
|
|
}
|
|
else if (event.type == ENET_EVENT_TYPE_RECEIVE)
|
|
{
|
|
Net_ParseServerPacket(&event);
|
|
}
|
|
|
|
enet_packet_destroy(event.packet);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Map Update Packets
|
|
|
|
|
|
//Insert a sprite from STAT_NETALLOC
|
|
static int32_t Net_DoInsertSprite(int32_t sect, int32_t stat)
|
|
{
|
|
int32_t i = headspritestat[STAT_NETALLOC];
|
|
|
|
// This means that we've run out of server-side actors
|
|
if (i < 0)
|
|
{
|
|
Net_Error_Disconnect("Out of server side actors");
|
|
return i;
|
|
}
|
|
|
|
changespritestat(i, stat);
|
|
do_insertsprite_at_headofsect(i, sect);
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Player Updates
|
|
|
|
static void Net_FillPlayerUpdate(playerupdate_t *update, int32_t player)
|
|
{
|
|
update->playerindex = player;
|
|
|
|
update->pos = g_player[player].ps->pos;
|
|
update->opos = g_player[player].ps->opos;
|
|
update->vel = g_player[player].ps->vel;
|
|
update->q16ang = g_player[player].ps->q16ang;
|
|
update->q16horiz = g_player[player].ps->q16horiz;
|
|
update->q16horizoff = g_player[player].ps->q16horizoff;
|
|
update->ping = g_player[player].ping;
|
|
update->deadflag = g_player[player].ps->dead_flag;
|
|
update->playerquitflag = g_player[player].playerquitflag;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// New Game Packets
|
|
|
|
// set all actors, walls, and sectors in a snapshot to their Null states.
|
|
static void Net_InitMapState(netmapstate_t* mapState)
|
|
{
|
|
netactor_t sprDefault = cNullNetActor;
|
|
|
|
int32_t index = 0;
|
|
|
|
sprDefault.netIndex = cSTOP_PARSING_CODE;
|
|
|
|
mapState->maxActorIndex = 0;
|
|
|
|
// this code is just a (slow) memset as it is now,
|
|
// it may be a good idea to use "baselines", which can reduce the amount
|
|
// of delta encoding when a sprite is first added. This
|
|
// could be a good optimization to consider later.
|
|
for (index = 0; index < MAXSPRITES; index++)
|
|
{
|
|
mapState->actor[index] = sprDefault;
|
|
}
|
|
|
|
for (index = 0; index < MAXWALLS; index++)
|
|
{
|
|
mapState->wall[index] = cNullNetWall;
|
|
}
|
|
|
|
for (index = 0; index < MAXSECTORS; index++)
|
|
{
|
|
mapState->sector[index] = cNullNetSector;
|
|
}
|
|
|
|
// set the revision number to a valid but easy to identify number,
|
|
// this is just to make it easier to debug.
|
|
mapState->revisionNumber = 8675309;
|
|
|
|
}
|
|
|
|
// Both client and server execute this
|
|
static void Net_ResetPlayers()
|
|
{
|
|
int32_t playerIndex;
|
|
|
|
for (TRAVERSE_CONNECT(playerIndex))
|
|
{
|
|
P_ResetWeapons(playerIndex);
|
|
P_ResetInventory(playerIndex);
|
|
g_player[playerIndex].revision = cInitialMapStateRevisionNumber;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Net_ResetPlayerReady()
|
|
{
|
|
int32_t playerindex;
|
|
|
|
for (TRAVERSE_CONNECT(playerindex))
|
|
{
|
|
g_player[playerindex].ready = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// Packet code
|
|
//---------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
// enforce signed/unsigned and overflow behavior for WriteBits smaller than 32
|
|
// remember that all of the WriteX functions only take 32 bit integers as input...
|
|
#define PICKY_TYPECHECK 1
|
|
|
|
#define MAX_PACKET_STRING 1024
|
|
|
|
#define LOG_MESSAGE_BITS 1
|
|
|
|
#define NetNumberOfIndexes (1 << NETINDEX_BITS)
|
|
#define IndexesOK (MAXWALLS < NetNumberOfIndexes) && (MAXSPRITES < NetNumberOfIndexes) && (MAXSECTORS < NetNumberOfIndexes)
|
|
|
|
#if(!IndexesOK)
|
|
#error "network.cpp: game arrays are now too big to send over the network, please update NETINDEX_BITS to be the right length to store a wall, sprite, and sector index"
|
|
#endif
|
|
|
|
#define STRUCTINDEX_BITS 8 // hopefully no game structs ever have more than 255 fields....
|
|
|
|
#define DEBUG_CHANGEDINDEXES 1
|
|
|
|
#define FLOAT_INT_BITS 13
|
|
|
|
// remember that the minimum negative number is the sign bit + all zeros
|
|
const int32_t cTruncInt_Min = -(1 << (FLOAT_INT_BITS - 1));
|
|
const int32_t cTruncInt_Max = (1 << (FLOAT_INT_BITS - 1)) - 1;
|
|
|
|
typedef struct NetBuffer_s
|
|
{
|
|
uint8_t *Data;
|
|
int32_t Bit;
|
|
int32_t ReadCurByte;
|
|
int32_t CurSize;
|
|
int32_t MaxSize; // set in NetBuffer_Init to the size of the byte array that this struct referrs to in Data
|
|
} NetBuffer_t;
|
|
|
|
// NOTE: does NOT fill byteArray with zeros
|
|
static void NetBuffer_Init(NetBuffer_t *buffer, uint8_t *byteArray, int32_t arrayLength)
|
|
{
|
|
// fill all the entries of the netbuffer struct (reminder: the byte array is not part of the struct)
|
|
Bmemset(buffer, 0, sizeof(*buffer));
|
|
buffer->Data = byteArray;
|
|
buffer->MaxSize = arrayLength;
|
|
}
|
|
|
|
|
|
///<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);
|
|
videoNextPage();
|
|
}
|
|
|
|
// XXX: this looks like something that should be rate limited...
|
|
packbuf[0] = PACKET_PLAYER_PING;
|
|
packbuf[1] = myconnectindex;
|
|
|
|
if (g_netClientPeer)
|
|
{
|
|
enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(&packbuf[0], 2, ENET_PACKET_FLAG_RELIABLE));
|
|
Dbg_PacketSent(PACKET_PLAYER_PING);
|
|
}
|
|
|
|
|
|
G_HandleAsync();
|
|
|
|
if (g_player[0].pingcnt > serverReady)
|
|
{
|
|
P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 8 + 2 + 1);
|
|
return;
|
|
}
|
|
} while (1);
|
|
}
|
|
|
|
void Net_ResetPrediction(void)
|
|
{
|
|
NET_75_CHECK++; // this function is probably not needed
|
|
}
|
|
|
|
void Net_Connect(const char *srvaddr)
|
|
{
|
|
ENetAddress address;
|
|
ENetEvent event;
|
|
char * addrstr = NULL;
|
|
|
|
const int32_t cNumberOfRetries = 4;
|
|
|
|
int32_t connectCount = cNumberOfRetries;
|
|
|
|
char *oursrvaddr = Xstrdup(srvaddr);
|
|
|
|
Net_Disconnect();
|
|
|
|
g_netClient = enet_host_create(NULL, 1, CHAN_MAX, 0, 0);
|
|
|
|
if (g_netClient == NULL)
|
|
{
|
|
initprintf("An error occurred while trying to create an ENet client host.\n");
|
|
return;
|
|
}
|
|
|
|
addrstr = strtok(oursrvaddr, ":");
|
|
enet_address_set_host(&address, addrstr);
|
|
addrstr = strtok(NULL, ":");
|
|
address.port = addrstr == NULL ? g_netPort : Batoi(addrstr);
|
|
|
|
g_netClientPeer = enet_host_connect(g_netClient, &address, CHAN_MAX, 0);
|
|
|
|
if (g_netClientPeer == NULL)
|
|
{
|
|
initprintf("No available peers for initiating an ENet connection.\n");
|
|
return;
|
|
}
|
|
|
|
for (connectCount = cNumberOfRetries; connectCount > 0; connectCount--)
|
|
{
|
|
/* Wait up to 5 seconds for the connection attempt to succeed. */
|
|
if (enet_host_service(g_netClient, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
|
|
{
|
|
initprintf("Connection to %s:%d succeeded.\n", oursrvaddr, address.port);
|
|
Xfree(oursrvaddr);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* Either the 5 seconds are up or a disconnect event was */
|
|
/* received. Reset the peer in the event the 5 seconds */
|
|
/* had run out without any significant event. */
|
|
enet_peer_reset(g_netClientPeer);
|
|
initprintf("Connection to %s:%d failed.\n", oursrvaddr, address.port);
|
|
}
|
|
initprintf(connectCount ? "Retrying...\n" : "Giving up connection attempt.\n");
|
|
}
|
|
|
|
// [75] note: it only gets here if there was an error
|
|
Xfree(oursrvaddr);
|
|
Net_Disconnect();
|
|
}
|
|
|
|
///< summary>
|
|
/// Record the current state of the game arrays to the initial snapshot
|
|
///</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 + (((int32_t) totalclock >> 3) % 7),
|
|
4 - (sintable[((int32_t) 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
|
|
|
|
//-------------------------------------------------------------------------------------------------
|