mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-29 23:22:01 +00:00
e8aa715763
GL: r_dynamic -1 is now r_temporalscenecache 1, which makes menu options etc a little friendlier. fixed a serious memory leak. GL: Lightmaps are now uploaded using pbos to reduce cpu stalls (especially with temporalscenecache) and the resulting periodic framerate drops. Requires gl4.4. PM: moved manifest-downloads to the package manager. still needs some proper testing. PM: Fixed bug with downloading updates from every known mirror for that update. PM: Fixed bug with duplicate mirrors... PM: menuqc is now able to query available updates. engine's Draw_TextBox centers the text box more appropriately and easily. SV: added sv_autooffload cvar, when set the map command will automatically create a server in a separate process to reduce the effects of stutter in inefficient ssqc mods. Menu: menu_mods now shares data with getgamedirinfo builtin. MenuQC: Added some extra properties to the getgamedirinfo builtin. MenuQC: Added Menu_RendererRestarted entrypoint. MenuQC: _vid_renderer_opts cvar now has a value that actually reflects the windowing systems in the build, rather than just renderers. CQSC: Added getlocationname builtin. ALSA: device names are now more consistent with other audio drivers. SV: added unsavegame console command, to delete unwanted saved games. SV: hashtable entries are now saved into saved games. SV: reworked player remapping strategy when loading games. Player slots are now directly swapped serverside, not reconnected. SV: resend all csqc entity state when a client signals that it started recording a demo. SV: Added SOLID_BSPTRIGGER as a shapely alternative to the aabb SOLID_TRIGGER. modelindex must still be set for this to work. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5668 fc73d0e0-1445-4013-8a0c-d673dee63da5
1776 lines
38 KiB
C
1776 lines
38 KiB
C
#include "quakedef.h"
|
|
|
|
//this file contains q3 netcode related things.
|
|
//field info, netchan, and the WriteBits stuff (which should probably be moved to common.c with the others)
|
|
//also contains vm filesystem
|
|
|
|
#if defined(HAVE_CLIENT) && defined(HAVE_SERVER)
|
|
#define NET_SendPacket(c,s,d,t) NET_SendPacket(((c)!=NS_CLIENT)?svs.sockets:cls.sockets,s,d,t)
|
|
#elif defined(HAVE_SERVER)
|
|
#define NET_SendPacket(c,s,d,t) NET_SendPacket(svs.sockets,s,d,t)
|
|
#else
|
|
#define NET_SendPacket(c,s,d,t) NET_SendPacket(cls.sockets,s,d,t)
|
|
#endif
|
|
|
|
#define MAX_VM_FILES 8
|
|
|
|
typedef struct {
|
|
char name[256];
|
|
vfsfile_t *file;
|
|
int accessmode;
|
|
int owner;
|
|
} vm_fopen_files_t;
|
|
vm_fopen_files_t vm_fopen_files[MAX_VM_FILES];
|
|
//FIXME: why does this not use the VFS system?
|
|
qofs_t VM_fopen (const char *name, int *handle, int fmode, int owner)
|
|
{
|
|
int i;
|
|
|
|
if (!handle)
|
|
return !!FS_FLocateFile(name, FSLF_IFFOUND, NULL);
|
|
|
|
*handle = 0;
|
|
|
|
for (i = 0; i < MAX_VM_FILES; i++)
|
|
if (!vm_fopen_files[i].file)
|
|
break;
|
|
|
|
if (i == MAX_VM_FILES) //too many already open
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (name[1] == ':' || //dos filename absolute path specified - reject.
|
|
*name == '\\' || *name == '/' || //absolute path was given - reject
|
|
strstr(name, "..")) //someone tried to be cleaver.
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
switch (fmode)
|
|
{
|
|
case VM_FS_READ:
|
|
vm_fopen_files[i].file = FS_OpenVFS(name, "rb", FS_GAME);
|
|
break;
|
|
case VM_FS_APPEND:
|
|
case VM_FS_APPEND_SYNC:
|
|
vm_fopen_files[i].file = FS_OpenVFS(name, "ab", FS_GAMEONLY);
|
|
break;
|
|
case VM_FS_WRITE:
|
|
vm_fopen_files[i].file = FS_OpenVFS(name, "wb", FS_GAMEONLY);
|
|
break;
|
|
default: //bad
|
|
return -1;
|
|
}
|
|
if (!vm_fopen_files[i].file)
|
|
return -1;
|
|
|
|
Q_strncpyz(vm_fopen_files[i].name, name, sizeof(vm_fopen_files[i].name));
|
|
vm_fopen_files[i].accessmode = fmode;
|
|
vm_fopen_files[i].owner = owner;
|
|
|
|
*handle = i+1;
|
|
return VFS_GETLEN(vm_fopen_files[i].file);
|
|
}
|
|
|
|
void VM_fclose (int fnum, int owner)
|
|
{
|
|
fnum--;
|
|
|
|
if (fnum < 0 || fnum >= MAX_VM_FILES)
|
|
return; //out of range
|
|
|
|
if (vm_fopen_files[fnum].owner != owner)
|
|
return; //cgs?
|
|
|
|
if (!vm_fopen_files[fnum].file)
|
|
return; //not open
|
|
VFS_CLOSE(vm_fopen_files[fnum].file);
|
|
vm_fopen_files[fnum].file = NULL;
|
|
}
|
|
|
|
int VM_FRead (char *dest, int quantity, int fnum, int owner)
|
|
{
|
|
fnum--;
|
|
if (fnum < 0 || fnum >= MAX_VM_FILES)
|
|
return 0; //out of range
|
|
|
|
if (vm_fopen_files[fnum].owner != owner)
|
|
return 0; //cgs?
|
|
|
|
if (!vm_fopen_files[fnum].file)
|
|
return 0; //not open
|
|
if (!vm_fopen_files[fnum].file->ReadBytes)
|
|
return 0;
|
|
return VFS_READ(vm_fopen_files[fnum].file, dest, quantity);
|
|
}
|
|
int VM_FWrite (const char *dest, int quantity, int fnum, int owner)
|
|
{
|
|
fnum--;
|
|
if (fnum < 0 || fnum >= MAX_VM_FILES)
|
|
return 0; //out of range
|
|
|
|
if (vm_fopen_files[fnum].owner != owner)
|
|
return 0; //cgs?
|
|
|
|
if (!vm_fopen_files[fnum].file)
|
|
return 0; //not open
|
|
if (!vm_fopen_files[fnum].file->WriteBytes)
|
|
return 0;
|
|
quantity = VFS_WRITE(vm_fopen_files[fnum].file, dest, quantity);
|
|
|
|
if (vm_fopen_files[fnum].accessmode == VM_FS_APPEND_SYNC)
|
|
VFS_FLUSH(vm_fopen_files[fnum].file);
|
|
return quantity;
|
|
}
|
|
qboolean VM_FSeek (int fnum, qofs_t offset, int seektype, int owner)
|
|
{
|
|
qofs_t fsize;
|
|
fnum--;
|
|
if (fnum < 0 || fnum >= MAX_VM_FILES)
|
|
return false; //out of range
|
|
if (vm_fopen_files[fnum].owner != owner)
|
|
return false; //cgs?
|
|
if (!vm_fopen_files[fnum].file)
|
|
return false; //not open
|
|
switch(vm_fopen_files[fnum].file->seekstyle)
|
|
{
|
|
case SS_SEEKABLE:
|
|
case SS_SLOW:
|
|
fsize = VFS_GETLEN(vm_fopen_files[fnum].file); //can't cache it if we're writing
|
|
switch(seektype)
|
|
{
|
|
case 0:
|
|
offset += VFS_TELL(vm_fopen_files[fnum].file);
|
|
return 0;
|
|
case 1:
|
|
offset += fsize;
|
|
break;
|
|
default:
|
|
case 2:
|
|
offset += 0;
|
|
break;
|
|
}
|
|
if (offset > fsize)
|
|
return false;
|
|
VFS_SEEK(vm_fopen_files[fnum].file, offset);
|
|
return true;
|
|
case SS_PIPE:
|
|
case SS_UNSEEKABLE:
|
|
return false;
|
|
}
|
|
return false; //should be unreachable.
|
|
}
|
|
qofs_t VM_FTell (int fnum, int owner)
|
|
{
|
|
fnum--;
|
|
if (fnum < 0 || fnum >= MAX_VM_FILES)
|
|
return 0; //out of range
|
|
if (vm_fopen_files[fnum].owner != owner)
|
|
return 0; //cgs?
|
|
if (!vm_fopen_files[fnum].file)
|
|
return 0; //not open
|
|
|
|
return VFS_TELL(vm_fopen_files[fnum].file);
|
|
}
|
|
void VM_fcloseall (int owner)
|
|
{
|
|
int i;
|
|
for (i = 1; i <= MAX_VM_FILES; i++)
|
|
{
|
|
VM_fclose(i, owner);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//filesystem searches result in a tightly-packed blob of null-terminated filenames (along with a count for how many entries)
|
|
//$modlist searches give both gamedir AND description strings (in that order) instead of just one string per entry (loaded via fs_game cvar along with a vid_restart).
|
|
typedef struct {
|
|
char *initialbuffer;
|
|
char *buffer;
|
|
char *dir;
|
|
int found;
|
|
int bufferleft;
|
|
int skip;
|
|
} vmsearch_t;
|
|
static int QDECL VMEnum(const char *match, qofs_t size, time_t mtime, void *args, searchpathfuncs_t *spath)
|
|
{
|
|
char *check;
|
|
int newlen;
|
|
match += ((vmsearch_t *)args)->skip;
|
|
newlen = strlen(match)+1;
|
|
if (newlen > ((vmsearch_t *)args)->bufferleft)
|
|
return false; //too many files for the buffer
|
|
|
|
check = ((vmsearch_t *)args)->initialbuffer;
|
|
while(check < ((vmsearch_t *)args)->buffer)
|
|
{
|
|
if (!stricmp(check, match))
|
|
return true; //we found this one already
|
|
check += strlen(check)+1;
|
|
}
|
|
|
|
memcpy(((vmsearch_t *)args)->buffer, match, newlen);
|
|
((vmsearch_t *)args)->buffer+=newlen;
|
|
((vmsearch_t *)args)->bufferleft-=newlen;
|
|
((vmsearch_t *)args)->found++;
|
|
return true;
|
|
}
|
|
|
|
static int QDECL IfFound(const char *match, qofs_t size, time_t modtime, void *args, searchpathfuncs_t *spath)
|
|
{
|
|
*(qboolean*)args = true;
|
|
return true;
|
|
}
|
|
|
|
static int QDECL VMEnumMods(const char *match, qofs_t size, time_t modtime, void *args, searchpathfuncs_t *spath)
|
|
{
|
|
char *check;
|
|
char desc[1024];
|
|
int newlen;
|
|
int desclen;
|
|
qboolean foundone;
|
|
vfsfile_t *f;
|
|
|
|
newlen = strlen(match)+1;
|
|
|
|
if (newlen <= 2)
|
|
return true;
|
|
|
|
//make sure match is a directory
|
|
if (match[newlen-2] != '/')
|
|
return true;
|
|
|
|
if (!stricmp(match, "baseq3/"))
|
|
return true; //we don't want baseq3. FIXME: should be any basedir, rather than hardcoded.
|
|
|
|
foundone = false;
|
|
Sys_EnumerateFiles(va("%s%s/", ((vmsearch_t *)args)->dir, match), "*.pk3", IfFound, &foundone, spath);
|
|
if (foundone == false)
|
|
return true; //we only count directories with a pk3 file
|
|
|
|
Q_strncpyz(desc, match, sizeof(desc));
|
|
f = FS_OpenVFS(va("%sdescription.txt", match), "rb", FS_ROOT);
|
|
if (f)
|
|
{
|
|
VFS_GETS(f, desc, sizeof(desc));
|
|
VFS_CLOSE(f);
|
|
}
|
|
|
|
desclen = strlen(desc)+1;
|
|
|
|
if (newlen+desclen+5 > ((vmsearch_t *)args)->bufferleft)
|
|
return false; //too many files for the buffer
|
|
|
|
check = ((vmsearch_t *)args)->initialbuffer;
|
|
while(check < ((vmsearch_t *)args)->buffer)
|
|
{
|
|
if (!stricmp(check, match))
|
|
return true; //we found this one already
|
|
check += strlen(check)+1;
|
|
check += strlen(check)+1;
|
|
}
|
|
|
|
memcpy(((vmsearch_t *)args)->buffer, match, newlen);
|
|
if (newlen > 1 && match[newlen-2] == '/')
|
|
((vmsearch_t *)args)->buffer[--newlen-1] = 0;
|
|
((vmsearch_t *)args)->buffer+=newlen;
|
|
((vmsearch_t *)args)->bufferleft-=newlen;
|
|
|
|
memcpy(((vmsearch_t *)args)->buffer, desc, desclen);
|
|
((vmsearch_t *)args)->buffer+=desclen;
|
|
((vmsearch_t *)args)->bufferleft-=desclen;
|
|
|
|
((vmsearch_t *)args)->found++;
|
|
return true;
|
|
}
|
|
|
|
int VM_GetFileList(const char *path, const char *ext, char *output, int buffersize)
|
|
{
|
|
vmsearch_t vms;
|
|
vms.initialbuffer = vms.buffer = output;
|
|
vms.skip = strlen(path)+1;
|
|
vms.bufferleft = buffersize;
|
|
vms.found=0;
|
|
if (!strcmp(path, "$modlist"))
|
|
{
|
|
vms.skip=0;
|
|
Sys_EnumerateFiles((vms.dir=com_gamepath), "*", VMEnumMods, &vms, NULL);
|
|
if (*com_homepath)
|
|
Sys_EnumerateFiles((vms.dir=com_homepath), "*", VMEnumMods, &vms, NULL);
|
|
}
|
|
else if (*(char *)ext == '.' || *(char *)ext == '/')
|
|
COM_EnumerateFiles(va("%s/*%s", path, ext), VMEnum, &vms);
|
|
else
|
|
COM_EnumerateFiles(va("%s/*.%s", path, ext), VMEnum, &vms);
|
|
return vms.found;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(Q3SERVER) || defined(Q3CLIENT)
|
|
|
|
#include "clq3defs.h" //okay, urr, this is bad for dedicated servers. urhum. Maybe they're not looking? It's only typedefs and one extern.
|
|
|
|
#define MAX_VMQ3_CVARS 512 //can be blindly increased
|
|
cvar_t *q3cvlist[MAX_VMQ3_CVARS];
|
|
int VMQ3_Cvar_Register(q3vmcvar_t *v, char *name, char *defval, int flags)
|
|
{
|
|
int i;
|
|
int fteflags = 0;
|
|
cvar_t *c;
|
|
|
|
fteflags = flags & (CVAR_ARCHIVE | CVAR_USERINFO | CVAR_SERVERINFO);
|
|
|
|
c = Cvar_Get(name, defval, fteflags, "Q3VM cvars");
|
|
if (!c) //command name, etc
|
|
return 0;
|
|
#ifndef SERVERONLY
|
|
if ((flags & CVAR_USERINFO) && !(c->flags & CVAR_USERINFO))
|
|
{
|
|
c->flags |= CVAR_USERINFO;
|
|
InfoBuf_SetKey(&cls.userinfo[0], c->name, c->string);
|
|
}
|
|
#endif
|
|
#ifndef CLIENTONLY
|
|
if ((flags & CVAR_SERVERINFO) && !(c->flags & CVAR_SERVERINFO))
|
|
{
|
|
c->flags |= CVAR_SERVERINFO;
|
|
InfoBuf_SetKey (&svs.info, c->name, c->string);
|
|
}
|
|
#endif
|
|
for (i = 0; i < MAX_VMQ3_CVARS; i++)
|
|
{
|
|
if (!q3cvlist[i])
|
|
q3cvlist[i] = c;
|
|
if (q3cvlist[i] == c)
|
|
{
|
|
if (v)
|
|
{
|
|
v->handle = i+1;
|
|
|
|
VMQ3_Cvar_Update(v);
|
|
}
|
|
return i+1;
|
|
}
|
|
}
|
|
|
|
Con_Printf("Ran out of VMQ3 cvar handles\n");
|
|
|
|
return 0;
|
|
}
|
|
int VMQ3_Cvar_Update(q3vmcvar_t *v)
|
|
{
|
|
cvar_t *c;
|
|
int i;
|
|
i = v->handle;
|
|
if (!i)
|
|
return 0; //not initialised
|
|
i--;
|
|
if ((unsigned)i >= MAX_VMQ3_CVARS)
|
|
return 0; //a hack attempt
|
|
|
|
c = q3cvlist[i];
|
|
if (!c)
|
|
return 0; //that slot isn't active yet
|
|
|
|
v->integer = c->ival;
|
|
v->value = c->value;
|
|
v->modificationCount = c->modified;
|
|
Q_strncpyz(v->string, c->string, sizeof(v->string));
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
MSG_WriteRawBytes
|
|
============
|
|
*/
|
|
static void MSG_WriteRawBytes( sizebuf_t *msg, int value, int bits )
|
|
{
|
|
qbyte *buf;
|
|
|
|
if( bits <= 8 )
|
|
{
|
|
buf = SZ_GetSpace( msg, 1 );
|
|
buf[0] = value;
|
|
}
|
|
else if( bits <= 16 )
|
|
{
|
|
buf = SZ_GetSpace( msg, 2 );
|
|
buf[0] = value & 0xFF;
|
|
buf[1] = value >> 8;
|
|
}
|
|
else if( bits <= 32 )
|
|
{
|
|
buf = SZ_GetSpace( msg, 4 );
|
|
buf[0] = value & 0xFF;
|
|
buf[1] = (value >> 8) & 0xFF;
|
|
buf[2] = (value >> 16) & 0xFF;
|
|
buf[3] = value >> 24;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
MSG_WriteRawBits
|
|
============
|
|
*/
|
|
static void MSG_WriteRawBits( sizebuf_t *msg, int value, int bits )
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
/*
|
|
============
|
|
MSG_WriteHuffBits
|
|
============
|
|
*/
|
|
#ifdef HUFFNETWORK
|
|
static void MSG_WriteHuffBits( sizebuf_t *msg, int value, int bits )
|
|
{
|
|
int remaining;
|
|
int i;
|
|
|
|
value &= 0xFFFFFFFFU >> (32 - bits);
|
|
remaining = bits & 7;
|
|
|
|
for( i=0; i<remaining ; i++ )
|
|
{
|
|
if( !(msg->currentbit & 7) )
|
|
{
|
|
msg->data[msg->currentbit >> 3] = 0;
|
|
}
|
|
msg->data[msg->currentbit >> 3] |= (value & 1) << (msg->currentbit & 7);
|
|
msg->currentbit++;
|
|
value >>= 1;
|
|
}
|
|
bits -= remaining;
|
|
|
|
if( bits > 0 )
|
|
{
|
|
for( i=0 ; i<(bits+7)>>3 ; i++ )
|
|
{
|
|
Huff_EmitByte( value & 255, msg->data, &msg->currentbit );
|
|
value >>= 8;
|
|
}
|
|
}
|
|
|
|
msg->cursize = (msg->currentbit >> 3) + 1;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
============
|
|
MSG_WriteBits
|
|
============
|
|
*/
|
|
void MSG_WriteBits(sizebuf_t *msg, int value, int bits)
|
|
{
|
|
#ifdef MSG_PROFILING
|
|
int maxval;
|
|
#endif // MSG_PROFILING
|
|
|
|
if( msg->maxsize - msg->cursize < 4 )
|
|
{
|
|
msg->overflowed = true;
|
|
return;
|
|
}
|
|
|
|
if( !bits || bits < -31 || bits > 32 )
|
|
{
|
|
Sys_Error("MSG_WriteBits: bad bits %i", bits);
|
|
}
|
|
|
|
#ifdef MSG_PROFILING
|
|
msg_bitsWritten += bits;
|
|
|
|
if( bits != 32 )
|
|
{
|
|
if( bits > 0 )
|
|
{
|
|
maxval = (1 << bits) - 1;
|
|
if( value > maxval || maxval < 0 )
|
|
{
|
|
msg_overflows++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
maxval = (1 << (bits - 1)) - 1;
|
|
if( value > maxval || value < -maxval - 1 )
|
|
{
|
|
msg_overflows++;
|
|
}
|
|
}
|
|
}
|
|
#endif // MSG_PROFILING
|
|
|
|
if( bits < 0 )
|
|
{
|
|
bits = -bits;
|
|
}
|
|
|
|
switch( msg->packing )
|
|
{
|
|
default:
|
|
case SZ_BAD:
|
|
Sys_Error("MSG_WriteBits: bad msg->packing %i", msg->packing );
|
|
break;
|
|
case SZ_RAWBYTES:
|
|
MSG_WriteRawBytes( msg, value, bits );
|
|
break;
|
|
case SZ_RAWBITS:
|
|
MSG_WriteRawBits( msg, value, bits );
|
|
break;
|
|
#ifdef HUFFNETWORK
|
|
case SZ_HUFFMAN:
|
|
MSG_WriteHuffBits( msg, value, bits );
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//q3 netchan
|
|
//note that the sv and cl both have their own wrappers, to handle encryption.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define MAX_PACKETLEN 1400
|
|
#define STUNDEMULTIPLEX_MASK 0x40000000 //stun requires that we set this bit to avoid surprises, and ignore packets without it.
|
|
#define FRAGMENT_MASK 0x80000000
|
|
#define FRAGMENTATION_TRESHOLD (MAX_PACKETLEN-100)
|
|
qboolean Netchan_ProcessQ3 (netchan_t *chan)
|
|
{
|
|
//incoming_reliable_sequence is perhaps wrongly used...
|
|
int sequence;
|
|
qboolean fragment;
|
|
int fragmentStart;
|
|
int fragmentLength;
|
|
char adr[MAX_ADR_SIZE];
|
|
|
|
// Get sequence number
|
|
MSG_BeginReading(msg_nullnetprim);
|
|
sequence = MSG_ReadBits(32);
|
|
|
|
if (chan->pext_stunaware)
|
|
{
|
|
sequence = BigLong(sequence);
|
|
if (sequence & STUNDEMULTIPLEX_MASK)
|
|
sequence-= STUNDEMULTIPLEX_MASK;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// Read the qport if we are a server
|
|
if (chan->sock == NS_SERVER)
|
|
{
|
|
MSG_ReadBits(16);
|
|
}
|
|
|
|
// Check if packet is a message fragment
|
|
if (sequence & FRAGMENT_MASK)
|
|
{
|
|
sequence &= ~FRAGMENT_MASK;
|
|
|
|
fragment = true;
|
|
fragmentStart = MSG_ReadBits(16);
|
|
fragmentLength = MSG_ReadBits(16);
|
|
}
|
|
else
|
|
{
|
|
fragment = false;
|
|
fragmentStart = 0;
|
|
fragmentLength = 0;
|
|
}
|
|
|
|
/* if (net_showpackets->integer)
|
|
{
|
|
if (fragment)
|
|
{
|
|
Con_Printf("%s recv %4i : s=%i fragment=%i,%i\n", (chan->sock == NS_CLIENT) ? "client" : "server", net_message.cursize, sequence, fragmentStart, fragmentLength);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("%s recv %4i : s=%i\n", (chan->sock == NS_CLIENT) ? "client" : "server", net_message.cursize, sequence);
|
|
}
|
|
}*/
|
|
|
|
// Discard stale or duplicated packets
|
|
if (sequence <= chan->incoming_sequence)
|
|
{
|
|
/* if (net_showdrop->integer || net_showpackets->integer)
|
|
{
|
|
Con_Printf("%s:Out of order packet %i at %i\n", NET_AdrToString(chan->remote_address), chan->incoming_sequence);
|
|
}*/
|
|
return false;
|
|
}
|
|
|
|
// Dropped packets don't keep the message from being used
|
|
chan->drop_count = sequence - (chan->incoming_sequence + 1);
|
|
|
|
if (chan->drop_count > 0)// && (net_showdrop->integer || net_showpackets->integer))
|
|
{
|
|
Con_DPrintf("%s:Dropped %i packets at %i\n", NET_AdrToString(adr, sizeof(adr), &chan->remote_address), chan->drop_count, sequence);
|
|
}
|
|
|
|
if (!fragment)
|
|
{ // not fragmented
|
|
chan->incoming_sequence = sequence;
|
|
chan->last_received = realtime;
|
|
return true;
|
|
}
|
|
|
|
// Check for new fragmented message
|
|
if (chan->incoming_reliable_sequence != sequence)
|
|
{
|
|
chan->incoming_reliable_sequence = sequence;
|
|
chan->in_fragment_length = 0;
|
|
}
|
|
|
|
// Check fragments sequence
|
|
if (chan->in_fragment_length != fragmentStart)
|
|
{
|
|
// if(net_showdrop->integer || net_showpackets->integer)
|
|
{
|
|
Con_Printf("%s:Dropped a message fragment\n", NET_AdrToString(adr, sizeof(adr), &chan->remote_address));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check if fragmentLength is valid
|
|
if (fragmentLength < 0 || fragmentLength > FRAGMENTATION_TRESHOLD || msg_readcount + fragmentLength > net_message.cursize || chan->in_fragment_length + fragmentLength > sizeof(chan->in_fragment_buf))
|
|
{
|
|
/* if (net_showdrop->integer || net_showpackets->integer)
|
|
{
|
|
Con_Printf("%s:illegal fragment length\n", NET_AdrToString(chan->remote_address));
|
|
}
|
|
*/ return false;
|
|
}
|
|
|
|
// Append to the incoming fragment buffer
|
|
memcpy( chan->in_fragment_buf + chan->in_fragment_length, net_message.data + msg_readcount, fragmentLength);
|
|
|
|
chan->in_fragment_length += fragmentLength;
|
|
if (fragmentLength == FRAGMENTATION_TRESHOLD)
|
|
{
|
|
return false; // there are more fragments of this message
|
|
}
|
|
|
|
// Check if assembled message fits in buffer
|
|
if (chan->in_fragment_length > net_message.maxsize)
|
|
{
|
|
Con_Printf("%s:fragmentLength %i > net_message.maxsize\n", NET_AdrToString(adr, sizeof(adr), &chan->remote_address), chan->in_fragment_length);
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Reconstruct message properly
|
|
//
|
|
SZ_Clear(&net_message);
|
|
MSG_WriteLong(&net_message, sequence);
|
|
SZ_Write(&net_message, chan->in_fragment_buf, chan->in_fragment_length);
|
|
|
|
MSG_BeginReading(msg_nullnetprim);
|
|
MSG_ReadLong();
|
|
|
|
// No more fragments
|
|
chan->in_fragment_length = 0;
|
|
chan->incoming_reliable_sequence = 0;
|
|
chan->incoming_sequence = sequence;
|
|
chan->last_received = realtime;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
Netchan_TransmitNextFragment
|
|
=================
|
|
*/
|
|
void Netchan_TransmitNextFragment( netchan_t *chan )
|
|
{
|
|
//'reliable' is badly named. it should be 'fragment' instead.
|
|
//but in the interests of a smaller netchan_t...
|
|
int i;
|
|
sizebuf_t send;
|
|
qbyte send_buf[MAX_PACKETLEN];
|
|
int fragmentLength;
|
|
|
|
int sequence = chan->outgoing_sequence | FRAGMENT_MASK;
|
|
if (chan->pext_stunaware)
|
|
{
|
|
sequence+= STUNDEMULTIPLEX_MASK;
|
|
sequence = BigLong(sequence);
|
|
}
|
|
|
|
// Write the packet header
|
|
memset(&send, 0, sizeof(send));
|
|
send.packing = SZ_RAWBYTES;
|
|
send.maxsize = sizeof(send_buf);
|
|
send.data = send_buf;
|
|
MSG_WriteLong( &send, sequence );
|
|
#ifndef SERVERONLY
|
|
// Send the qport if we are a client
|
|
if( chan->sock == NS_CLIENT )
|
|
{
|
|
MSG_WriteShort( &send, chan->qport);
|
|
}
|
|
#endif
|
|
fragmentLength = chan->reliable_length - chan->reliable_start;
|
|
if( fragmentLength > FRAGMENTATION_TRESHOLD ) {
|
|
// remaining fragment is still too large
|
|
fragmentLength = FRAGMENTATION_TRESHOLD;
|
|
}
|
|
|
|
// Write the fragment header
|
|
MSG_WriteShort( &send, chan->reliable_start );
|
|
MSG_WriteShort( &send, fragmentLength );
|
|
|
|
// Copy message fragment to the packet
|
|
SZ_Write( &send, chan->reliable_buf + chan->reliable_start, fragmentLength );
|
|
|
|
// Send the datagram
|
|
NET_SendPacket( chan->sock, send.cursize, send.data, &chan->remote_address );
|
|
|
|
// if( net_showpackets->integer )
|
|
// {
|
|
// Con_Printf( "%s send %4i : s=%i fragment=%i,%i\n", (chan->sock == NS_CLIENT) ? "client" : "server", send.cursize, chan->outgoing_sequence, chan->reliable_start, fragmentLength );
|
|
// }
|
|
|
|
// Even if we have sent the whole message,
|
|
// but if fragmentLength == FRAGMENTATION_TRESHOLD we have to write empty
|
|
// fragment later, because Netchan_Process expects it...
|
|
chan->reliable_start += fragmentLength;
|
|
if( chan->reliable_start == chan->reliable_length && fragmentLength != FRAGMENTATION_TRESHOLD )
|
|
{
|
|
// we have sent the whole message!
|
|
chan->outgoing_sequence++;
|
|
chan->reliable_length = 0;
|
|
chan->reliable_start = 0;
|
|
|
|
i = chan->outgoing_sequence & (MAX_LATENT-1);
|
|
chan->outgoing_size[i] = send.cursize;
|
|
chan->outgoing_time[i] = realtime;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Netchan_Transmit
|
|
=================
|
|
*/
|
|
void Netchan_TransmitQ3( netchan_t *chan, int length, const qbyte *data )
|
|
{
|
|
int i;
|
|
sizebuf_t send;
|
|
qbyte send_buf[MAX_OVERALLMSGLEN+6];
|
|
char adr[MAX_ADR_SIZE];
|
|
int sequence;
|
|
|
|
// Check for message overflow
|
|
if( length > MAX_OVERALLMSGLEN )
|
|
{
|
|
Con_Printf( "%s: outgoing message overflow\n", NET_AdrToString( adr, sizeof(adr), &chan->remote_address ) );
|
|
return;
|
|
}
|
|
|
|
if( length < 0 )
|
|
{
|
|
Sys_Error("Netchan_Transmit: length = %i", length);
|
|
}
|
|
|
|
// Don't send if there are still unsent fragments
|
|
if( chan->reliable_length )
|
|
{
|
|
Netchan_TransmitNextFragment( chan );
|
|
if( chan->reliable_length )
|
|
{
|
|
Con_DPrintf( "%s: unsent fragments\n", NET_AdrToString( adr, sizeof(adr), &chan->remote_address ) );
|
|
return;
|
|
}
|
|
/*drop the outgoing packet if we fragmented*/
|
|
/*failure to do this results in the wrong encoding due to the outgoing sequence*/
|
|
return;
|
|
}
|
|
|
|
// See if this message is too large and should be fragmented
|
|
if( length >= FRAGMENTATION_TRESHOLD )
|
|
{
|
|
chan->reliable_length = length;
|
|
chan->reliable_start = 0;
|
|
memcpy( chan->reliable_buf, data, length );
|
|
Netchan_TransmitNextFragment( chan );
|
|
return;
|
|
}
|
|
|
|
sequence = chan->outgoing_sequence;
|
|
if (chan->pext_stunaware)
|
|
{
|
|
sequence+= STUNDEMULTIPLEX_MASK;
|
|
sequence = BigLong(sequence);
|
|
}
|
|
|
|
// Write the packet header
|
|
memset(&send, 0, sizeof(send));
|
|
send.packing = SZ_RAWBYTES;
|
|
send.maxsize = sizeof(send_buf);
|
|
send.data = send_buf;
|
|
MSG_WriteLong( &send, chan->outgoing_sequence );
|
|
#ifndef SERVERONLY
|
|
// Send the qport if we are a client
|
|
if( chan->sock == NS_CLIENT )
|
|
{
|
|
MSG_WriteShort( &send, chan->qport);
|
|
}
|
|
#endif
|
|
// Copy the message to the packet
|
|
SZ_Write( &send, data, length );
|
|
|
|
// Send the datagram
|
|
NET_SendPacket( chan->sock, send.cursize, send.data, &chan->remote_address );
|
|
|
|
/* if( net_showpackets->integer )
|
|
{
|
|
Con_Printf( "%s send %4i : s=%i ack=%i\n", (chan->sock == NS_SERVER) ? "server" : "client", send.cursize , chan->outgoing_sequence, chan->incoming_sequence );
|
|
}
|
|
*/
|
|
chan->outgoing_sequence++;
|
|
|
|
i = chan->outgoing_sequence & (MAX_LATENT-1);
|
|
chan->outgoing_size[i] = send.cursize;
|
|
chan->outgoing_time[i] = realtime;
|
|
}
|
|
|
|
|
|
//////////////
|
|
|
|
|
|
int StringKey( const char *string, int length )
|
|
{
|
|
int i;
|
|
int key = 0;
|
|
|
|
for( i=0 ; i<length && string[i] ; i++ )
|
|
{
|
|
key += string[i] * (119 + i);
|
|
}
|
|
|
|
return (key ^ (key >> 10) ^ (key >> 20));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
#ifdef MSG_SHOWNET
|
|
const char *name;
|
|
#endif // MSG_SHOWNET
|
|
int offset;
|
|
int bits; // bits > 0 --> unsigned integer
|
|
// bits = 0 --> float value
|
|
// bits < 0 --> signed integer
|
|
} q3field_t;
|
|
|
|
// field declarations
|
|
#ifdef MSG_SHOWNET
|
|
# define PS_FIELD(n,b) { #n, ((size_t)&(((q3playerState_t *)0)->n)), b }
|
|
# define ES_FIELD(n,b) { #n, ((size_t)&(((q3entityState_t *)0)->n)), b }
|
|
#else
|
|
# define PS_FIELD(n,b) { ((size_t)&(((q3playerState_t *)0)->n)), b }
|
|
# define ES_FIELD(n,b) { ((size_t)&(((q3entityState_t *)0)->n)), b }
|
|
#endif
|
|
|
|
// field data accessing
|
|
#define FIELD_INTEGER(s) (*(int *)((qbyte *)(s)+field->offset))
|
|
#define FIELD_FLOAT(s) (*(float *)((qbyte *)(s)+field->offset))
|
|
|
|
#define SNAPPED_BITS 13
|
|
#define MAX_SNAPPED (1<<SNAPPED_BITS)
|
|
|
|
|
|
//
|
|
// entityState_t
|
|
//
|
|
static const q3field_t esFieldTable[] = {
|
|
ES_FIELD( pos.trTime, 32 ),
|
|
ES_FIELD( pos.trBase[0], 0 ),
|
|
ES_FIELD( pos.trBase[1], 0 ),
|
|
ES_FIELD( pos.trDelta[0], 0 ),
|
|
ES_FIELD( pos.trDelta[1], 0 ),
|
|
ES_FIELD( pos.trBase[2], 0 ),
|
|
ES_FIELD( apos.trBase[1], 0 ),
|
|
ES_FIELD( pos.trDelta[2], 0 ),
|
|
ES_FIELD( apos.trBase[0], 0 ),
|
|
ES_FIELD( event, 10 ),
|
|
ES_FIELD( angles2[1], 0 ),
|
|
ES_FIELD( eType, 8 ),
|
|
ES_FIELD( torsoAnim, 8 ),
|
|
ES_FIELD( eventParm, 8 ),
|
|
ES_FIELD( legsAnim, 8 ),
|
|
ES_FIELD( groundEntityNum, 10 ),
|
|
ES_FIELD( pos.trType, 8 ),
|
|
ES_FIELD( eFlags, 19 ),
|
|
ES_FIELD( otherEntityNum, 10 ),
|
|
ES_FIELD( weapon, 8 ),
|
|
ES_FIELD( clientNum, 8 ),
|
|
ES_FIELD( angles[1], 0 ),
|
|
ES_FIELD( pos.trDuration, 32 ),
|
|
ES_FIELD( apos.trType, 8 ),
|
|
ES_FIELD( origin[0], 0 ),
|
|
ES_FIELD( origin[1], 0 ),
|
|
ES_FIELD( origin[2], 0 ),
|
|
ES_FIELD( solid, 24 ),
|
|
ES_FIELD( powerups, 16 ),
|
|
ES_FIELD( modelindex, 8 ),
|
|
ES_FIELD( otherEntityNum2, 10 ),
|
|
ES_FIELD( loopSound, 8 ),
|
|
ES_FIELD( generic1, 8 ),
|
|
ES_FIELD( origin2[2], 0 ),
|
|
ES_FIELD( origin2[0], 0 ),
|
|
ES_FIELD( origin2[1], 0 ),
|
|
ES_FIELD( modelindex2, 8 ),
|
|
ES_FIELD( angles[0], 0 ),
|
|
ES_FIELD( time, 32 ),
|
|
ES_FIELD( apos.trTime, 32 ),
|
|
ES_FIELD( apos.trDuration, 32 ),
|
|
ES_FIELD( apos.trBase[2], 0 ),
|
|
ES_FIELD( apos.trDelta[0], 0 ),
|
|
ES_FIELD( apos.trDelta[1], 0 ),
|
|
ES_FIELD( apos.trDelta[2], 0 ),
|
|
ES_FIELD( time2, 32 ),
|
|
ES_FIELD( angles[2], 0 ),
|
|
ES_FIELD( angles2[0], 0 ),
|
|
ES_FIELD( angles2[2], 0 ),
|
|
ES_FIELD( constantLight, 32 ),
|
|
ES_FIELD( frame, 16 )
|
|
};
|
|
|
|
static const int esTableSize = sizeof( esFieldTable ) / sizeof( esFieldTable[0] );
|
|
|
|
q3entityState_t nullEntityState;
|
|
|
|
/*
|
|
============
|
|
MSG_ReadDeltaEntity
|
|
|
|
'from' == NULL --> nodelta update
|
|
'to' == NULL --> do nothing
|
|
|
|
returns false if the ent was removed.
|
|
============
|
|
*/
|
|
#ifndef SERVERONLY
|
|
qboolean MSG_Q3_ReadDeltaEntity( const q3entityState_t *from, q3entityState_t *to, int number )
|
|
{
|
|
const q3field_t *field;
|
|
int to_integer;
|
|
int maxFieldNum;
|
|
#ifdef MSG_SHOWNET
|
|
int startbits;
|
|
qboolean dump;
|
|
#endif
|
|
int i;
|
|
|
|
|
|
if( number < 0 || number >= MAX_GENTITIES )
|
|
{
|
|
Host_EndGame("MSG_ReadDeltaEntity: Bad delta entity number: %i\n", number);
|
|
}
|
|
|
|
if( !to )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#ifdef MSG_SHOWNET
|
|
dump = (qboolean)(cl_shownet->integer >= 2);
|
|
|
|
if( dump )
|
|
{
|
|
startbits = msg->bit;
|
|
}
|
|
#endif
|
|
|
|
if (MSG_ReadBits(1))
|
|
{
|
|
memset( to, 0, sizeof( *to ) );
|
|
to->number = ENTITYNUM_NONE;
|
|
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "%3i: #%-3i remove\n", msg->readcount, number );
|
|
}
|
|
#endif
|
|
return false; // removed
|
|
}
|
|
|
|
if( !from )
|
|
{
|
|
memset( to, 0, sizeof( *to ) );
|
|
}
|
|
else
|
|
{
|
|
memcpy( to, from, sizeof( *to ) );
|
|
}
|
|
to->number = number;
|
|
|
|
if( !MSG_ReadBits( 1 ) )
|
|
{
|
|
return true; // unchanged
|
|
}
|
|
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "%3i: #%-3i ", msg->readcount, to->number );
|
|
}
|
|
#endif
|
|
|
|
maxFieldNum = MSG_ReadByte();
|
|
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "<%i> ", maxFieldNum );
|
|
}
|
|
#endif
|
|
|
|
if( maxFieldNum > esTableSize )
|
|
{
|
|
Host_EndGame("MSG_ReadDeltaEntity: maxFieldNum > esTableSize");
|
|
}
|
|
|
|
for( i=0, field=esFieldTable ; i<maxFieldNum ; i++, field++ )
|
|
{
|
|
if( !MSG_ReadBits( 1 ) )
|
|
continue; // field unchanged
|
|
|
|
if( !MSG_ReadBits( 1 ) )
|
|
{
|
|
FIELD_INTEGER( to ) = 0;
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "%s:%i ", field->name, 0 );
|
|
}
|
|
#endif
|
|
continue; // field set to zero
|
|
}
|
|
|
|
if( field->bits )
|
|
{
|
|
to_integer = MSG_ReadBits( field->bits );
|
|
FIELD_INTEGER( to ) = to_integer;
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "%s:%i ", field->name, to_integer );
|
|
}
|
|
#endif
|
|
continue; // integer value
|
|
}
|
|
|
|
|
|
if( !MSG_ReadBits( 1 ) )
|
|
{
|
|
to_integer = MSG_ReadBits( 13 ) - 0x1000;
|
|
FIELD_FLOAT( to ) = (float)to_integer;
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "%s:%i ", field->name, to_integer );
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
FIELD_INTEGER( to ) = MSG_ReadLong();
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "%s:%f ", field->name, FIELD_FLOAT( to ) );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( " (%i bits)\n", msg->bit - startbits );
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
============
|
|
MSG_WriteDeltaEntity
|
|
|
|
If 'force' parm is false, this won't result any bits
|
|
emitted if entity didn't changed at all
|
|
|
|
'from' == NULL --> nodelta update
|
|
'to' == NULL --> entity removed
|
|
============
|
|
*/
|
|
#ifndef CLIENTONLY
|
|
void MSGQ3_WriteDeltaEntity(sizebuf_t *msg, const q3entityState_t *from, const q3entityState_t *to, qboolean force)
|
|
{
|
|
const q3field_t *field;
|
|
int to_value;
|
|
int to_integer;
|
|
float to_float;
|
|
int maxFieldNum;
|
|
int i;
|
|
|
|
if(!to)
|
|
{
|
|
if(from)
|
|
{
|
|
MSG_WriteBits(msg, from->number, GENTITYNUM_BITS);
|
|
MSG_WriteBits(msg, 1, 1);
|
|
}
|
|
return; // removed
|
|
}
|
|
|
|
if(to->number < 0 || to->number > MAX_GENTITIES)
|
|
SV_Error("MSG_WriteDeltaEntity: Bad entity number: %i", to->number);
|
|
|
|
if(!from)
|
|
from = &nullEntityState; // nodelta update
|
|
|
|
//
|
|
// find last modified field in table
|
|
//
|
|
maxFieldNum = 0;
|
|
for(i=0, field=esFieldTable; i<esTableSize; i++, field++ )
|
|
{
|
|
if( FIELD_INTEGER( from ) != FIELD_INTEGER(to))
|
|
maxFieldNum = i + 1;
|
|
}
|
|
|
|
if(!maxFieldNum)
|
|
{
|
|
if(!force)
|
|
return; // don't emit any bits at all
|
|
|
|
MSG_WriteBits(msg, to->number, GENTITYNUM_BITS);
|
|
MSG_WriteBits(msg, 0, 1);
|
|
MSG_WriteBits(msg, 0, 1);
|
|
return; // unchanged
|
|
}
|
|
|
|
MSG_WriteBits(msg, to->number, GENTITYNUM_BITS);
|
|
MSG_WriteBits(msg, 0, 1);
|
|
MSG_WriteBits(msg, 1, 1);
|
|
MSG_WriteBits(msg, maxFieldNum, 8);
|
|
|
|
//
|
|
// write all modified fields
|
|
//
|
|
for(i=0, field=esFieldTable; i<maxFieldNum ; i++, field++)
|
|
{
|
|
to_value = FIELD_INTEGER(to);
|
|
|
|
if(FIELD_INTEGER(from) == to_value)
|
|
{
|
|
MSG_WriteBits( msg, 0, 1 );
|
|
continue; // field unchanged
|
|
}
|
|
MSG_WriteBits(msg, 1, 1);
|
|
|
|
if(!to_value)
|
|
{
|
|
MSG_WriteBits(msg, 0, 1);
|
|
continue; // field set to zero
|
|
}
|
|
MSG_WriteBits(msg, 1, 1);
|
|
|
|
if(field->bits)
|
|
{
|
|
MSG_WriteBits(msg, to_value, field->bits);
|
|
continue; // integer value
|
|
}
|
|
|
|
//
|
|
// figure out how to pack float value
|
|
//
|
|
to_float = FIELD_FLOAT(to);
|
|
to_integer = (int)to_float;
|
|
|
|
#ifdef MSG_PROFILING
|
|
msg_vectorsEmitted++;
|
|
#endif // MSG_PROFILING
|
|
|
|
if((float)to_integer == to_float
|
|
&& to_integer + MAX_SNAPPED/2 >= 0
|
|
&& to_integer + MAX_SNAPPED/2 < MAX_SNAPPED)
|
|
{
|
|
MSG_WriteBits(msg, 0, 1 ); // pack in 13 bits
|
|
MSG_WriteBits(msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS);
|
|
|
|
#ifdef MSG_PROFILING
|
|
msg_vectorsCompressed++;
|
|
#endif // MSG_PROFILING
|
|
|
|
} else {
|
|
MSG_WriteBits(msg, 1, 1 ); // pack in 32 bits
|
|
MSG_WriteBits(msg, to_value, 32);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
//player state
|
|
|
|
|
|
//
|
|
// playerState_t
|
|
//
|
|
static const q3field_t psFieldTable[] = {
|
|
PS_FIELD( commandTime, 32 ),
|
|
PS_FIELD( origin[0], 0 ),
|
|
PS_FIELD( origin[1], 0 ),
|
|
PS_FIELD( bobCycle, 8 ),
|
|
PS_FIELD( velocity[0], 0 ),
|
|
PS_FIELD( velocity[1], 0 ),
|
|
PS_FIELD( viewangles[1], 0 ),
|
|
PS_FIELD( viewangles[0], 0 ),
|
|
PS_FIELD( weaponTime, -16 ),
|
|
PS_FIELD( origin[2], 0 ),
|
|
PS_FIELD( velocity[2], 0 ),
|
|
PS_FIELD( legsTimer, 8 ),
|
|
PS_FIELD( pm_time, -16 ),
|
|
PS_FIELD( eventSequence, 16 ),
|
|
PS_FIELD( torsoAnim, 8 ),
|
|
PS_FIELD( movementDir, 4 ),
|
|
PS_FIELD( events[0], 8 ),
|
|
PS_FIELD( legsAnim, 8 ),
|
|
PS_FIELD( events[1], 8 ),
|
|
PS_FIELD( pm_flags, 16 ),
|
|
PS_FIELD( groundEntityNum, 10 ),
|
|
PS_FIELD( weaponstate, 4 ),
|
|
PS_FIELD( eFlags, 16 ),
|
|
PS_FIELD( externalEvent, 10 ),
|
|
PS_FIELD( gravity, 16 ),
|
|
PS_FIELD( speed, 16 ),
|
|
PS_FIELD( delta_angles[1], 16 ),
|
|
PS_FIELD( externalEventParm, 8 ),
|
|
PS_FIELD( viewheight, -8 ),
|
|
PS_FIELD( damageEvent, 8 ),
|
|
PS_FIELD( damageYaw, 8 ),
|
|
PS_FIELD( damagePitch, 8 ),
|
|
PS_FIELD( damageCount, 8 ),
|
|
PS_FIELD( generic1, 8 ),
|
|
PS_FIELD( pm_type, 8 ),
|
|
PS_FIELD( delta_angles[0], 16 ),
|
|
PS_FIELD( delta_angles[2], 16 ),
|
|
PS_FIELD( torsoTimer, 12 ),
|
|
PS_FIELD( eventParms[0], 8 ),
|
|
PS_FIELD( eventParms[1], 8 ),
|
|
PS_FIELD( clientNum, 8 ),
|
|
PS_FIELD( weapon, 5 ),
|
|
PS_FIELD( viewangles[2], 0 ),
|
|
PS_FIELD( grapplePoint[0], 0 ),
|
|
PS_FIELD( grapplePoint[1], 0 ),
|
|
PS_FIELD( grapplePoint[2], 0 ),
|
|
PS_FIELD( jumppad_ent, 10 ),
|
|
PS_FIELD( loopSound, 16 )
|
|
};
|
|
static const int psTableSize = sizeof( psFieldTable ) / sizeof( psFieldTable[0] );
|
|
q3playerState_t nullPlayerState;
|
|
|
|
/*
|
|
============
|
|
MSG_WriteDeltaPlayerstate
|
|
|
|
'from' == NULL --> nodelta update
|
|
'to' == NULL --> do nothing
|
|
============
|
|
*/
|
|
#ifndef CLIENTONLY
|
|
void MSGQ3_WriteDeltaPlayerstate(sizebuf_t *msg, const q3playerState_t *from, const q3playerState_t *to)
|
|
{
|
|
const q3field_t *field;
|
|
int to_value;
|
|
float to_float;
|
|
int to_integer;
|
|
int maxFieldNum;
|
|
int statsMask;
|
|
int persistantMask;
|
|
int ammoMask;
|
|
int powerupsMask;
|
|
int i;
|
|
|
|
if(!to)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!from)
|
|
{
|
|
from = &nullPlayerState; // nodelta update
|
|
}
|
|
|
|
//
|
|
// find last modified field in table
|
|
//
|
|
maxFieldNum = 0;
|
|
for(i=0, field=psFieldTable ; i<psTableSize ; i++, field++)
|
|
{
|
|
if(FIELD_INTEGER(from) != FIELD_INTEGER(to))
|
|
{
|
|
maxFieldNum = i + 1;
|
|
}
|
|
}
|
|
|
|
MSG_WriteBits(msg, maxFieldNum, 8);
|
|
|
|
//
|
|
// write all modified fields
|
|
//
|
|
for( i=0, field=psFieldTable ; i<maxFieldNum ; i++, field++ )
|
|
{
|
|
to_value = FIELD_INTEGER( to );
|
|
|
|
if( FIELD_INTEGER( from ) == to_value )
|
|
{
|
|
MSG_WriteBits( msg, 0, 1 );
|
|
continue; // field unchanged
|
|
}
|
|
MSG_WriteBits( msg, 1, 1 );
|
|
|
|
if( field->bits )
|
|
{
|
|
MSG_WriteBits( msg, to_value, field->bits );
|
|
continue; // integer value
|
|
}
|
|
|
|
//
|
|
// figure out how to pack float value
|
|
//
|
|
to_float = FIELD_FLOAT( to );
|
|
to_integer = (int)to_float;
|
|
|
|
#ifdef MSG_PROFILING
|
|
msg_vectorsEmitted++;
|
|
#endif // MSG_PROFILING
|
|
|
|
if( (float)to_integer == to_float
|
|
&& to_integer + MAX_SNAPPED/2 >= 0
|
|
&& to_integer + MAX_SNAPPED/2 < MAX_SNAPPED )
|
|
{
|
|
MSG_WriteBits( msg, 0, 1 ); // pack in 13 bits
|
|
MSG_WriteBits( msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS );
|
|
|
|
#ifdef MSG_PROFILING
|
|
msg_vectorsCompressed++;
|
|
#endif // MSG_PROFILING
|
|
|
|
} else {
|
|
MSG_WriteBits(msg, 1, 1); // pack in 32 bits
|
|
MSG_WriteBits(msg, to_value, 32);
|
|
}
|
|
}
|
|
|
|
//
|
|
// find modified arrays
|
|
//
|
|
statsMask = 0;
|
|
for(i=0; i<MAX_Q3_STATS; i++)
|
|
{
|
|
if(from->stats[i] != to->stats[i])
|
|
statsMask |= (1 << i);
|
|
}
|
|
|
|
persistantMask = 0;
|
|
for(i=0 ; i<MAX_Q3_PERSISTANT ; i++)
|
|
{
|
|
if(from->persistant[i] != to->persistant[i])
|
|
persistantMask |= (1 << i);
|
|
}
|
|
|
|
ammoMask = 0;
|
|
for(i=0 ; i<MAX_Q3_WEAPONS ; i++ )
|
|
{
|
|
if(from->ammo[i] != to->ammo[i])
|
|
ammoMask |= (1 << i);
|
|
}
|
|
|
|
powerupsMask = 0;
|
|
for( i=0 ; i<MAX_Q3_POWERUPS ; i++ )
|
|
{
|
|
if(from->powerups[i] != to->powerups[i])
|
|
powerupsMask |= (1 << i);
|
|
}
|
|
|
|
if(!statsMask && !persistantMask && !ammoMask && !powerupsMask)
|
|
{
|
|
MSG_WriteBits(msg, 0, 1);
|
|
return; // no arrays modified
|
|
}
|
|
|
|
//
|
|
// write all modified arrays
|
|
//
|
|
MSG_WriteBits(msg, 1, 1);
|
|
|
|
// PS_STATS
|
|
if(statsMask)
|
|
{
|
|
MSG_WriteBits(msg, 1, 1);
|
|
MSG_WriteBits(msg, statsMask, 16);
|
|
for(i=0; i<MAX_Q3_STATS; i++)
|
|
if(statsMask & (1 << i))
|
|
MSG_WriteBits(msg, to->stats[i], -16);
|
|
}
|
|
else
|
|
MSG_WriteBits(msg, 0, 1); // unchanged
|
|
|
|
// PS_PERSISTANT
|
|
if(persistantMask)
|
|
{
|
|
MSG_WriteBits(msg, 1, 1);
|
|
MSG_WriteBits(msg, persistantMask, 16);
|
|
for(i=0; i<MAX_Q3_PERSISTANT; i++)
|
|
if(persistantMask & (1 << i))
|
|
MSG_WriteBits(msg, to->persistant[i], -16);
|
|
}
|
|
else
|
|
MSG_WriteBits(msg, 0, 1); // unchanged
|
|
|
|
|
|
// PS_AMMO
|
|
if( ammoMask )
|
|
{
|
|
MSG_WriteBits(msg, 1, 1);
|
|
MSG_WriteBits(msg, ammoMask, 16);
|
|
for(i=0; i<MAX_Q3_WEAPONS; i++)
|
|
if(ammoMask & (1 << i))
|
|
MSG_WriteBits(msg, to->ammo[i], 16);
|
|
}
|
|
else
|
|
MSG_WriteBits(msg, 0, 1); // unchanged
|
|
|
|
// PS_POWERUPS
|
|
if(powerupsMask)
|
|
{
|
|
MSG_WriteBits(msg, 1, 1);
|
|
MSG_WriteBits(msg, powerupsMask, 16);
|
|
for(i=0; i<MAX_Q3_POWERUPS; i++)
|
|
{
|
|
if(powerupsMask & (1 << i))
|
|
MSG_WriteBits(msg, to->powerups[i], 32); // WARNING: powerups use 32 bits, not 16
|
|
}
|
|
}
|
|
else
|
|
MSG_WriteBits( msg, 0, 1 ); // unchanged
|
|
}
|
|
#endif
|
|
|
|
#ifndef SERVERONLY
|
|
void MSG_Q3_ReadDeltaPlayerstate( const q3playerState_t *from, q3playerState_t *to ) {
|
|
const q3field_t *field;
|
|
int to_integer;
|
|
int maxFieldNum;
|
|
int bitmask;
|
|
#ifdef MSG_SHOWNET
|
|
int startbits;
|
|
qboolean dump;
|
|
qboolean moredump;
|
|
#endif
|
|
int i;
|
|
|
|
if( !to )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef MSG_SHOWNET
|
|
dump = (qboolean)(cl_shownet->integer >= 2);
|
|
moredump = (qboolean)(cl_shownet->integer >= 4);
|
|
|
|
if( dump )
|
|
{
|
|
startbits = msg->bit;
|
|
|
|
Com_Printf( "%3i: playerstate ", msg->readcount );
|
|
}
|
|
#endif
|
|
|
|
if( !from )
|
|
{
|
|
memset( to, 0, sizeof( *to ) );
|
|
}
|
|
else
|
|
{
|
|
memcpy( to, from, sizeof( *to ) );
|
|
}
|
|
|
|
maxFieldNum = MSG_ReadByte();
|
|
|
|
if( maxFieldNum > psTableSize )
|
|
{
|
|
Host_EndGame( "MSG_ReadDeltaPlayerstate: maxFieldNum > psTableSize" );
|
|
}
|
|
|
|
for( i=0, field=psFieldTable ; i<maxFieldNum ; i++, field++ )
|
|
{
|
|
if(!MSG_ReadBits(1))
|
|
{
|
|
continue; // field unchanged
|
|
}
|
|
|
|
if( field->bits )
|
|
{
|
|
to_integer = MSG_ReadBits(field->bits);
|
|
FIELD_INTEGER( to ) = to_integer;
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Com_Printf( "%s:%i ", field->name, to_integer );
|
|
}
|
|
#endif
|
|
continue; // integer value
|
|
}
|
|
|
|
|
|
if(!MSG_ReadBits(1))
|
|
{
|
|
to_integer = MSG_ReadBits(13) - 0x1000;
|
|
FIELD_FLOAT( to ) = (float)to_integer;
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Com_Printf( "%s:%i ", field->name, to_integer );
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
FIELD_INTEGER( to ) = MSG_ReadLong();
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Com_Printf( "%s:%f ", field->name, FIELD_FLOAT( to ) );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if( MSG_ReadBits(1) )
|
|
{
|
|
// PS_STATS
|
|
if( MSG_ReadBits(1) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump )
|
|
{
|
|
Com_Printf( "PS_STATS " );
|
|
}
|
|
#endif
|
|
bitmask = MSG_ReadBits(16);
|
|
for( i=0 ; i<MAX_Q3_STATS ; i++ )
|
|
{
|
|
if( bitmask & (1 << i) )
|
|
{
|
|
to->stats[i] = (signed short)MSG_ReadBits(-16);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PS_PERSISTANT
|
|
if( MSG_ReadBits(1 ) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump )
|
|
{
|
|
Com_Printf( "PS_PERSISTANT " );
|
|
}
|
|
#endif
|
|
|
|
bitmask = MSG_ReadBits(16);
|
|
for( i=0 ; i<MAX_Q3_PERSISTANT ; i++ )
|
|
{
|
|
if( bitmask & (1 << i) )
|
|
{
|
|
to->persistant[i] = (signed short)MSG_ReadBits(-16);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PS_AMMO
|
|
if( MSG_ReadBits(1) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump )
|
|
{
|
|
Com_Printf( "PS_AMMO " );
|
|
}
|
|
#endif
|
|
|
|
bitmask = MSG_ReadBits(16);
|
|
for( i=0 ; i<MAX_Q3_WEAPONS ; i++ )
|
|
{
|
|
if( bitmask & (1 << i) )
|
|
{
|
|
to->ammo[i] = (signed short)MSG_ReadBits(16);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PS_POWERUPS
|
|
if( MSG_ReadBits(1) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump ) {
|
|
Com_Printf( "PS_POWERUPS " );
|
|
}
|
|
#endif
|
|
|
|
bitmask = MSG_ReadBits(16);
|
|
for( i=0 ; i<MAX_Q3_POWERUPS ; i++ )
|
|
{
|
|
if( bitmask & (1 << i) )
|
|
{
|
|
to->powerups[i] = MSG_ReadLong();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Com_Printf( " (%i bits)\n", msg->bit - startbits );
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//user commands
|
|
|
|
int kbitmask[] = {
|
|
0,
|
|
0x00000001, 0x00000003, 0x00000007, 0x0000000F,
|
|
0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF,
|
|
0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF,
|
|
0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF,
|
|
0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF,
|
|
0x001FFFFf, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF,
|
|
0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF,
|
|
0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,
|
|
};
|
|
|
|
static int MSG_ReadDeltaKey(int key, int from, int bits)
|
|
{
|
|
if (MSG_ReadBits(1))
|
|
return MSG_ReadBits(bits) ^ (key & kbitmask[bits]);
|
|
else
|
|
return from;
|
|
}
|
|
void MSG_Q3_ReadDeltaUsercmd(int key, const usercmd_t *from, usercmd_t *to)
|
|
{
|
|
if (MSG_ReadBits(1))
|
|
to->servertime = MSG_ReadBits(8) + from->servertime;
|
|
else
|
|
to->servertime = MSG_ReadBits(32);
|
|
to->msec = 0; //first of a packet should always be an absolute value, which makes the old value awkward.
|
|
|
|
if (!MSG_ReadBits(1))
|
|
{
|
|
to->angles[0] = from->angles[0];
|
|
to->angles[1] = from->angles[1];
|
|
to->angles[2] = from->angles[2];
|
|
to->forwardmove = from->forwardmove;
|
|
to->sidemove = from->sidemove;
|
|
to->upmove = from->upmove;
|
|
to->buttons = from->buttons;
|
|
to->weapon = from->weapon;
|
|
}
|
|
else
|
|
{
|
|
key ^= to->servertime;
|
|
to->angles[0] = MSG_ReadDeltaKey(key, from->angles[0], 16);
|
|
to->angles[1] = MSG_ReadDeltaKey(key, from->angles[1], 16);
|
|
to->angles[2] = MSG_ReadDeltaKey(key, from->angles[2], 16);
|
|
//yeah, this is messy
|
|
to->forwardmove = (signed char)(unsigned char)MSG_ReadDeltaKey(key, (unsigned char)(signed char)from->forwardmove, 8);
|
|
to->sidemove = (signed char)(unsigned char)MSG_ReadDeltaKey(key, (unsigned char)(signed char)from->sidemove, 8);
|
|
to->upmove = (signed char)(unsigned char)MSG_ReadDeltaKey(key, (unsigned char)(signed char)from->upmove, 8);
|
|
to->buttons = MSG_ReadDeltaKey(key, from->buttons, 16);
|
|
to->weapon = MSG_ReadDeltaKey(key, from->weapon, 8);
|
|
}
|
|
}
|
|
|
|
qint64_t Q3VM_GetRealtime(q3time_t *qtime)
|
|
{ //this is useful mostly for saved games, or other weird stuff.
|
|
time_t t = time(NULL);
|
|
if (qtime)
|
|
{
|
|
struct tm *tm = localtime(&t);
|
|
if (tm)
|
|
{
|
|
qtime->tm_sec = tm->tm_sec;
|
|
qtime->tm_hour = tm->tm_hour;
|
|
qtime->tm_mday = tm->tm_mday;
|
|
qtime->tm_mon = tm->tm_mon;
|
|
qtime->tm_year = tm->tm_year;
|
|
qtime->tm_wday = tm->tm_wday;
|
|
qtime->tm_yday = tm->tm_yday;
|
|
qtime->tm_isdst = tm->tm_isdst;
|
|
}
|
|
else
|
|
memset(qtime, 0, sizeof(*qtime));
|
|
}
|
|
return t;
|
|
}
|
|
#endif
|