1776 lines
40 KiB
C
1776 lines
40 KiB
C
#include "q3common.h"
|
|
plug2dfuncs_t *drawfuncs;
|
|
plug3dfuncs_t *scenefuncs;
|
|
plugaudiofuncs_t *audiofuncs;
|
|
plugq3vmfuncs_t *vmfuncs;
|
|
plugfsfuncs_t *fsfuncs;
|
|
pluginputfuncs_t *inputfuncs;
|
|
plugclientfuncs_t *clientfuncs;
|
|
plugmsgfuncs_t *msgfuncs;
|
|
plugworldfuncs_t *worldfuncs;
|
|
plugmasterfuncs_t *masterfuncs;
|
|
plugthreadfuncs_t *threadfuncs;
|
|
|
|
#ifndef STATIC_Q3
|
|
double realtime;
|
|
struct netprim_s msg_nullnetprim;
|
|
#endif
|
|
|
|
#ifdef HAVE_SERVER
|
|
//mostly for access to sv.state or svs.sockets
|
|
q3serverstate_t sv3;
|
|
#endif
|
|
|
|
//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
|
|
|
|
#define MAX_VM_FILES 64
|
|
|
|
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 !!fsfuncs->LocateFile(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 = fsfuncs->OpenVFS(name, "rb", FS_GAME);
|
|
break;
|
|
case VM_FS_APPEND:
|
|
case VM_FS_APPEND_SYNC:
|
|
vm_fopen_files[i].file = fsfuncs->OpenVFS(name, "ab", FS_GAMEONLY);
|
|
break;
|
|
case VM_FS_WRITE:
|
|
vm_fopen_files[i].file = fsfuncs->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;
|
|
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 (!Q_strcasecmp(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)
|
|
{
|
|
const int continuesearch = true;
|
|
const int abortsearch = false;
|
|
char *check;
|
|
char desc[1024];
|
|
int newlen;
|
|
int desclen;
|
|
qboolean foundone;
|
|
vfsfile_t *f;
|
|
|
|
newlen = strlen(match)+1;
|
|
|
|
if (newlen <= 2)
|
|
return continuesearch;
|
|
|
|
//make sure match is a directory
|
|
if (match[newlen-2] != '/')
|
|
return continuesearch;
|
|
|
|
if (!Q_strcasecmp(match, "baseq3/"))
|
|
return continuesearch; //we don't want baseq3. FIXME: should be any basedir, rather than hardcoded.
|
|
|
|
foundone = false;
|
|
fsfuncs->EnumerateFiles(FS_ROOT, va("%s/*.pk3", match), IfFound, &foundone);
|
|
if (foundone == false)
|
|
return continuesearch; //we only count directories with a pk3 file
|
|
|
|
Q_strncpyz(desc, match, sizeof(desc));
|
|
f = fsfuncs->OpenVFS(va("%sdescription.txt", match), "rb", FS_ROOT);
|
|
if (f)
|
|
{
|
|
char *e;
|
|
VFS_READ(f, desc, sizeof(desc)-1);
|
|
VFS_CLOSE(f);
|
|
desc[sizeof(desc)-1] = 0;
|
|
for (e = desc; *e; e++)
|
|
if (*e == '\n' || *e == '\r')
|
|
{
|
|
*e = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
desclen = strlen(desc)+1;
|
|
|
|
if (newlen+desclen+5 > ((vmsearch_t *)args)->bufferleft)
|
|
return abortsearch; //too many files for the buffer
|
|
|
|
check = ((vmsearch_t *)args)->initialbuffer;
|
|
while(check < ((vmsearch_t *)args)->buffer)
|
|
{
|
|
if (!Q_strcasecmp(check, match))
|
|
return continuesearch; //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 continuesearch;
|
|
}
|
|
|
|
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;
|
|
fsfuncs->EnumerateFiles(FS_ROOT, "*", VMEnumMods, &vms);
|
|
}
|
|
else if (*(char *)ext == '.' || *(char *)ext == '/')
|
|
fsfuncs->EnumerateFiles(FS_GAME, va("%s/*%s", path, ext), VMEnum, &vms);
|
|
else
|
|
fsfuncs->EnumerateFiles(FS_GAME, 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
|
|
static 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 = cvarfuncs->GetNVFDG(name, defval, fteflags, NULL, "Q3VM cvars");
|
|
if (!c) //command name, etc
|
|
return 0;
|
|
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;
|
|
if (!v)
|
|
return 0; //ERROR!
|
|
i = v->handle-1;
|
|
if ((unsigned)i >= MAX_VMQ3_CVARS)
|
|
return 0; //a hack attempt
|
|
|
|
c = q3cvlist[i];
|
|
if (!c)
|
|
return 0; //that slot isn't active yet
|
|
// if (v->modificationCount == c->modifiedcount)
|
|
// return 1; //no changes, don't waste time on an strcpy
|
|
|
|
v->integer = c->ival;
|
|
v->value = c->value;
|
|
v->modificationCount = c->modifiedcount;
|
|
Q_strncpyz(v->string, c->string, sizeof(v->string));
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//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)
|
|
void Netchan_SetupQ3(netsrc_t sock, netchan_t *chan, netadr_t *adr, int qport)
|
|
{
|
|
memset (chan, 0, sizeof(*chan));
|
|
|
|
chan->sock = sock;
|
|
chan->remote_address = *adr;
|
|
chan->last_received = realtime;
|
|
chan->incoming_unreliable = -1;
|
|
|
|
chan->message.data = chan->message_buf;
|
|
chan->message.allowoverflow = true;
|
|
chan->message.maxsize = MAX_QWMSGLEN;
|
|
|
|
chan->qport = qport;
|
|
|
|
chan->qportsize = 2;
|
|
}
|
|
qboolean Netchan_ProcessQ3 (netchan_t *chan, sizebuf_t *msg)
|
|
{
|
|
//incoming_reliable_sequence is perhaps wrongly used...
|
|
int sequence;
|
|
qboolean fragment;
|
|
int fragmentStart;
|
|
int fragmentLength;
|
|
char adr[MAX_ADR_SIZE];
|
|
|
|
// Get sequence number
|
|
msgfuncs->BeginReading(msg, msg_nullnetprim);
|
|
sequence = msgfuncs->ReadBits(32);
|
|
|
|
if (chan->pext_stunaware)
|
|
{
|
|
sequence = ((sequence&0xff000000)>>24)|((sequence&0x00ff0000)>>8)|((sequence&0x0000ff00)<<8)|((sequence&0x000000ff)<<24); //reinterpret it as big-endian
|
|
if (sequence & STUNDEMULTIPLEX_MASK)
|
|
sequence-= STUNDEMULTIPLEX_MASK;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// Read the qport if we are a server
|
|
if (chan->sock == NS_SERVER)
|
|
{
|
|
msgfuncs->ReadBits(16);
|
|
}
|
|
|
|
// Check if packet is a message fragment
|
|
if (sequence & FRAGMENT_MASK)
|
|
{
|
|
sequence &= ~FRAGMENT_MASK;
|
|
|
|
fragment = true;
|
|
fragmentStart = msgfuncs->ReadBits(16);
|
|
fragmentLength = msgfuncs->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", masterfuncs->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", masterfuncs->AdrToString(adr, sizeof(adr), &chan->remote_address));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check if fragmentLength is valid
|
|
if (fragmentLength < 0 || fragmentLength > FRAGMENTATION_TRESHOLD || msgfuncs->ReadCount() + fragmentLength > msg->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, msg->data + msgfuncs->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 > msg->maxsize)
|
|
{
|
|
Con_Printf("%s:fragmentLength %i > net_message.maxsize\n", masterfuncs->AdrToString(adr, sizeof(adr), &chan->remote_address), chan->in_fragment_length);
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Reconstruct message properly
|
|
//
|
|
msgfuncs->BeginWriting(msg, msg_nullnetprim, NULL, 0);
|
|
msgfuncs->WriteLong(msg, sequence);
|
|
msgfuncs->WriteData(msg, chan->in_fragment_buf, chan->in_fragment_length);
|
|
|
|
msgfuncs->BeginReading(msg, msg_nullnetprim);
|
|
msgfuncs->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(struct ftenet_connections_s *socket, 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 = ((sequence&0xff000000)>>24)|((sequence&0x00ff0000)>>8)|((sequence&0x0000ff00)<<8)|((sequence&0x000000ff)<<24); //reinterpret it as big-endian
|
|
}
|
|
|
|
// Write the packet header
|
|
memset(&send, 0, sizeof(send));
|
|
send.packing = SZ_RAWBYTES;
|
|
send.maxsize = sizeof(send_buf);
|
|
send.data = send_buf;
|
|
msgfuncs->WriteLong( &send, sequence );
|
|
#ifndef SERVERONLY
|
|
// Send the qport if we are a client
|
|
if( chan->sock == NS_CLIENT )
|
|
{
|
|
msgfuncs->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
|
|
msgfuncs->WriteShort( &send, chan->reliable_start );
|
|
msgfuncs->WriteShort( &send, fragmentLength );
|
|
|
|
// Copy message fragment to the packet
|
|
msgfuncs->WriteData(&send, chan->reliable_buf + chan->reliable_start, fragmentLength);
|
|
|
|
// Send the datagram
|
|
msgfuncs->SendPacket(socket, 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(struct ftenet_connections_s *socket, 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", masterfuncs->AdrToString( adr, sizeof(adr), &chan->remote_address ) );
|
|
return;
|
|
}
|
|
|
|
if( length < 0 )
|
|
{
|
|
plugfuncs->Error("Netchan_Transmit: length = %i", length);
|
|
}
|
|
|
|
// Don't send if there are still unsent fragments
|
|
if( chan->reliable_length )
|
|
{
|
|
Netchan_TransmitNextFragment(socket, chan);
|
|
if( chan->reliable_length )
|
|
{
|
|
Con_DPrintf( "%s: unsent fragments\n", masterfuncs->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(socket, chan);
|
|
return;
|
|
}
|
|
|
|
sequence = chan->outgoing_sequence;
|
|
if (chan->pext_stunaware)
|
|
{
|
|
sequence+= STUNDEMULTIPLEX_MASK;
|
|
sequence = ((sequence&0xff000000)>>24)|((sequence&0x00ff0000)>>8)|((sequence&0x0000ff00)<<8)|((sequence&0x000000ff)<<24); //reinterpret it as big-endian
|
|
}
|
|
|
|
// Write the packet header
|
|
msgfuncs->BeginWriting(&send, msg_nullnetprim, send_buf, sizeof(send_buf));
|
|
msgfuncs->WriteLong(&send, chan->outgoing_sequence);
|
|
#ifndef SERVERONLY
|
|
// Send the qport if we are a client
|
|
if( chan->sock == NS_CLIENT )
|
|
{
|
|
msgfuncs->WriteShort(&send, chan->qport);
|
|
}
|
|
#endif
|
|
// Copy the message to the packet
|
|
msgfuncs->WriteData(&send, data, length);
|
|
|
|
// Send the datagram
|
|
msgfuncs->SendPacket( socket, 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, MAX_Q3_POWERUPS ),
|
|
ES_FIELD( modelindex, MODELINDEX_BITS ),
|
|
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, MODELINDEX_BITS ),
|
|
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 )
|
|
{
|
|
plugfuncs->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 (msgfuncs->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( !msgfuncs->ReadBits( 1 ) )
|
|
{
|
|
return true; // unchanged
|
|
}
|
|
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "%3i: #%-3i ", msg->readcount, to->number );
|
|
}
|
|
#endif
|
|
|
|
maxFieldNum = msgfuncs->ReadByte();
|
|
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Con_Printf( "<%i> ", maxFieldNum );
|
|
}
|
|
#endif
|
|
|
|
if( maxFieldNum > esTableSize )
|
|
{
|
|
plugfuncs->EndGame("MSG_ReadDeltaEntity: maxFieldNum > esTableSize");
|
|
}
|
|
|
|
for( i=0, field=esFieldTable ; i<maxFieldNum ; i++, field++ )
|
|
{
|
|
if( !msgfuncs->ReadBits( 1 ) )
|
|
continue; // field unchanged
|
|
|
|
if( !msgfuncs->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 = msgfuncs->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( !msgfuncs->ReadBits( 1 ) )
|
|
{
|
|
to_integer = msgfuncs->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 ) = msgfuncs->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)
|
|
{
|
|
msgfuncs->WriteBits(msg, from->number, GENTITYNUM_BITS);
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
}
|
|
return; // removed
|
|
}
|
|
|
|
if(to->number < 0 || to->number > MAX_GENTITIES)
|
|
plugfuncs->EndGame("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
|
|
|
|
msgfuncs->WriteBits(msg, to->number, GENTITYNUM_BITS);
|
|
msgfuncs->WriteBits(msg, 0, 1);
|
|
msgfuncs->WriteBits(msg, 0, 1);
|
|
return; // unchanged
|
|
}
|
|
|
|
msgfuncs->WriteBits(msg, to->number, GENTITYNUM_BITS);
|
|
msgfuncs->WriteBits(msg, 0, 1);
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
msgfuncs->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)
|
|
{
|
|
msgfuncs->WriteBits( msg, 0, 1 );
|
|
continue; // field unchanged
|
|
}
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
|
|
if(!to_value)
|
|
{
|
|
msgfuncs->WriteBits(msg, 0, 1);
|
|
continue; // field set to zero
|
|
}
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
|
|
if(field->bits)
|
|
{
|
|
msgfuncs->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)
|
|
{
|
|
msgfuncs->WriteBits(msg, 0, 1 ); // pack in 13 bits
|
|
msgfuncs->WriteBits(msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS);
|
|
|
|
#ifdef MSG_PROFILING
|
|
msg_vectorsCompressed++;
|
|
#endif // MSG_PROFILING
|
|
|
|
} else {
|
|
msgfuncs->WriteBits(msg, 1, 1 ); // pack in 32 bits
|
|
msgfuncs->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;
|
|
unsigned int statsMask;
|
|
unsigned int persistantMask;
|
|
unsigned int ammoMask;
|
|
unsigned int powerupsMask;
|
|
int i, j;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
msgfuncs->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 )
|
|
{
|
|
msgfuncs->WriteBits( msg, 0, 1 );
|
|
continue; // field unchanged
|
|
}
|
|
msgfuncs->WriteBits( msg, 1, 1 );
|
|
|
|
if( field->bits )
|
|
{
|
|
msgfuncs->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 )
|
|
{
|
|
msgfuncs->WriteBits( msg, 0, 1 ); // pack in 13 bits
|
|
msgfuncs->WriteBits( msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS );
|
|
|
|
#ifdef MSG_PROFILING
|
|
msg_vectorsCompressed++;
|
|
#endif // MSG_PROFILING
|
|
|
|
} else {
|
|
msgfuncs->WriteBits(msg, 1, 1); // pack in 32 bits
|
|
msgfuncs->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 |= (1u << i);
|
|
}
|
|
|
|
persistantMask = 0;
|
|
for(i=0 ; i<MAX_Q3_PERSISTANT ; i++)
|
|
{
|
|
if(from->persistant[i] != to->persistant[i])
|
|
persistantMask |= (1u << i);
|
|
}
|
|
|
|
ammoMask = 0;
|
|
for(i=0 ; i<MAX_Q3_WEAPONS ; i++ )
|
|
{
|
|
if(from->ammo[i] != to->ammo[i])
|
|
ammoMask |= (1u << i);
|
|
}
|
|
|
|
powerupsMask = 0;
|
|
for( i=0 ; i<MAX_Q3_POWERUPS ; i++ )
|
|
{
|
|
if(from->powerups[i] != to->powerups[i])
|
|
powerupsMask |= (1u << i);
|
|
}
|
|
|
|
if(statsMask || persistantMask
|
|
|| ammoMask
|
|
|| powerupsMask)
|
|
{
|
|
//
|
|
// write all modified arrays
|
|
//
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
|
|
// PS_STATS
|
|
if (statsMask)
|
|
{
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
msgfuncs->WriteBits(msg, statsMask, 16);
|
|
for(i=0; i<MAX_Q3_STATS; i++)
|
|
if(statsMask & (1 << i))
|
|
msgfuncs->WriteBits(msg, to->stats[i], -16);
|
|
}
|
|
else
|
|
msgfuncs->WriteBits(msg, 0, 1); // unchanged
|
|
|
|
// PS_PERSISTANT
|
|
if (persistantMask)
|
|
{
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
msgfuncs->WriteBits(msg, persistantMask, 16);
|
|
for(i=0; i<MAX_Q3_PERSISTANT; i++)
|
|
if(persistantMask & (1 << i))
|
|
msgfuncs->WriteBits(msg, to->persistant[i], -16);
|
|
}
|
|
else
|
|
msgfuncs->WriteBits(msg, 0, 1); // unchanged
|
|
|
|
for (j = 0; j < MAX_Q3_WEAPONS/16; j++)
|
|
{
|
|
// PS_AMMO
|
|
if (ammoMask & (0xffffu<<j))
|
|
{
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
msgfuncs->WriteBits(msg, ammoMask>>(j*16), 16);
|
|
for(i=0; i<16; i++)
|
|
if(ammoMask & ((quint64_t)1u << (j*16+i)))
|
|
msgfuncs->WriteBits(msg, to->ammo[j*16+i], 16);
|
|
}
|
|
else
|
|
msgfuncs->WriteBits(msg, 0, 1); // unchanged
|
|
}
|
|
|
|
// PS_POWERUPS
|
|
if(powerupsMask)
|
|
{
|
|
msgfuncs->WriteBits(msg, 1, 1);
|
|
msgfuncs->WriteBits(msg, powerupsMask, 16);
|
|
for(i=0; i<MAX_Q3_POWERUPS; i++)
|
|
{
|
|
if(powerupsMask & (1 << i))
|
|
msgfuncs->WriteBits(msg, to->powerups[i], 32); // WARNING: powerups use 32 bits, not 16
|
|
}
|
|
}
|
|
else
|
|
msgfuncs->WriteBits(msg, 0, 1); // unchanged
|
|
}
|
|
else
|
|
msgfuncs->WriteBits(msg, 0, 1);
|
|
|
|
}
|
|
#endif
|
|
|
|
#ifndef SERVERONLY
|
|
void MSG_Q3_ReadDeltaPlayerstate( const q3playerState_t *from, q3playerState_t *to ) {
|
|
const q3field_t *field;
|
|
int to_integer;
|
|
int maxFieldNum;
|
|
unsigned int bitmask;
|
|
#ifdef MSG_SHOWNET
|
|
int startbits;
|
|
qboolean dump;
|
|
qboolean moredump;
|
|
#endif
|
|
int i, j;
|
|
|
|
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 = msgfuncs->ReadByte();
|
|
|
|
if( maxFieldNum > psTableSize )
|
|
{
|
|
plugfuncs->EndGame( "MSG_ReadDeltaPlayerstate: maxFieldNum > psTableSize" );
|
|
}
|
|
|
|
for( i=0, field=psFieldTable ; i<maxFieldNum ; i++, field++ )
|
|
{
|
|
if(!msgfuncs->ReadBits(1))
|
|
{
|
|
continue; // field unchanged
|
|
}
|
|
|
|
if( field->bits )
|
|
{
|
|
to_integer = msgfuncs->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(!msgfuncs->ReadBits(1))
|
|
{
|
|
to_integer = msgfuncs->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 ) = msgfuncs->ReadLong();
|
|
#ifdef MSG_SHOWNET
|
|
if( dump )
|
|
{
|
|
Com_Printf( "%s:%f ", field->name, FIELD_FLOAT( to ) );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if( msgfuncs->ReadBits(1) )
|
|
{
|
|
// PS_STATS
|
|
if( msgfuncs->ReadBits(1) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump )
|
|
{
|
|
Com_Printf( "PS_STATS " );
|
|
}
|
|
#endif
|
|
bitmask = msgfuncs->ReadBits(16);
|
|
for( i=0 ; i<MAX_Q3_STATS ; i++ )
|
|
{
|
|
if( bitmask & (1 << i) )
|
|
{
|
|
to->stats[i] = (signed short)msgfuncs->ReadBits(-16);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PS_PERSISTANT
|
|
if( msgfuncs->ReadBits(1 ) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump )
|
|
{
|
|
Com_Printf( "PS_PERSISTANT " );
|
|
}
|
|
#endif
|
|
|
|
bitmask = msgfuncs->ReadBits(16);
|
|
for( i=0 ; i<MAX_Q3_PERSISTANT ; i++ )
|
|
{
|
|
if( bitmask & (1 << i) )
|
|
{
|
|
to->persistant[i] = (signed short)msgfuncs->ReadBits(-16);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PS_AMMO
|
|
for (j=0; j < MAX_Q3_WEAPONS/16; j++)
|
|
{
|
|
if( msgfuncs->ReadBits(1) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump )
|
|
{
|
|
Com_Printf( "PS_AMMO " );
|
|
}
|
|
#endif
|
|
bitmask = msgfuncs->ReadBits(16);
|
|
for( i=0 ; i<16 ; i++ )
|
|
{
|
|
if( bitmask & (1u << i) )
|
|
{
|
|
to->ammo[j*16+i] = (signed short)msgfuncs->ReadBits(16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// PS_POWERUPS
|
|
if( msgfuncs->ReadBits(1) )
|
|
{
|
|
#ifdef MSG_SHOWNET
|
|
if( moredump ) {
|
|
Com_Printf( "PS_POWERUPS " );
|
|
}
|
|
#endif
|
|
|
|
bitmask = msgfuncs->ReadBits(16);
|
|
for( i=0 ; i<MAX_Q3_POWERUPS ; i++ )
|
|
{
|
|
if( bitmask & (1 << i) )
|
|
{
|
|
to->powerups[i] = msgfuncs->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 (msgfuncs->ReadBits(1))
|
|
return msgfuncs->ReadBits(bits) ^ (key & kbitmask[bits]);
|
|
else
|
|
return from;
|
|
}
|
|
void MSG_Q3_ReadDeltaUsercmd(int key, const usercmd_t *from, usercmd_t *to)
|
|
{
|
|
if (msgfuncs->ReadBits(1))
|
|
to->servertime = msgfuncs->ReadBits(8) + from->servertime;
|
|
else
|
|
to->servertime = msgfuncs->ReadBits(32);
|
|
to->msec = 0; //first of a packet should always be an absolute value, which makes the old value awkward.
|
|
|
|
if (!msgfuncs->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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct q3gamecode_s q3funcs =
|
|
{
|
|
#ifdef HAVE_CLIENT
|
|
{
|
|
CLQ3_SendAuthPacket,
|
|
CLQ3_SendConnectPacket,
|
|
CLQ3_Established,
|
|
CLQ3_SendClientCommand,
|
|
CLQ3_SendCmd,
|
|
CLQ3_ParseServerMessage,
|
|
CLQ3_Disconnect,
|
|
},
|
|
|
|
{
|
|
CG_Restart,
|
|
CG_Refresh,
|
|
CG_ConsoleCommand,
|
|
CG_KeyPressed,
|
|
CG_GatherLoopingSounds,
|
|
},
|
|
|
|
{
|
|
UI_IsRunning,
|
|
UI_ConsoleCommand,
|
|
UI_Start,
|
|
UI_OpenMenu,
|
|
UI_Reset,
|
|
},
|
|
#else
|
|
{NULL},{NULL},{NULL},
|
|
#endif
|
|
|
|
#ifdef HAVE_SERVER
|
|
{
|
|
SVQ3_ShutdownGame,
|
|
SVQ3_InitGame,
|
|
SVQ3_ConsoleCommand,
|
|
SVQ3_PrefixedConsoleCommand,
|
|
SVQ3_HandleClient,
|
|
SVQ3_DirectConnect,
|
|
SVQ3_NewMapConnects,
|
|
SVQ3_DropClient,
|
|
SVQ3_RunFrame,
|
|
SVQ3_SendMessage,
|
|
SVQ3_RestartGamecode,
|
|
SVQ3_ServerinfoChanged,
|
|
},
|
|
#else
|
|
{NULL},
|
|
#endif
|
|
};
|
|
|
|
#ifndef STATIC_Q3
|
|
void Q3_Frame(double enginetime, double gametime)
|
|
{
|
|
realtime = enginetime;
|
|
}
|
|
#endif
|
|
|
|
void Q3_Shutdown(void)
|
|
{
|
|
#ifdef HAVE_SERVER
|
|
SVQ3_ShutdownGame(false);
|
|
#endif
|
|
#ifdef HAVE_CLIENT
|
|
CG_Stop();
|
|
UI_Stop();
|
|
|
|
VMQ3_FlushStringHandles();
|
|
#endif
|
|
}
|
|
|
|
#ifdef STATIC_Q3
|
|
#define Plug_Init Plug_Q3_Init
|
|
#endif
|
|
|
|
qboolean Plug_Init(void)
|
|
{
|
|
vmfuncs = plugfuncs->GetEngineInterface(plugq3vmfuncs_name, sizeof(*vmfuncs));
|
|
fsfuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs));
|
|
msgfuncs = plugfuncs->GetEngineInterface(plugmsgfuncs_name, sizeof(*msgfuncs));
|
|
worldfuncs = plugfuncs->GetEngineInterface(plugworldfuncs_name, sizeof(*worldfuncs));
|
|
threadfuncs = plugfuncs->GetEngineInterface(plugthreadfuncs_name, sizeof(*threadfuncs));
|
|
|
|
if (!vmfuncs || !fsfuncs || !msgfuncs || !worldfuncs/* || !threadfuncs -- checked on use*/)
|
|
{
|
|
Con_Printf("Engine functionality missing, cannot enable q3 gamecode support.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!plugfuncs->ExportFunction("Shutdown", Q3_Shutdown) ||
|
|
!plugfuncs->ExportInterface("Quake3Plugin", &q3funcs, sizeof(q3funcs)))
|
|
{
|
|
Con_Printf("Engine is already using a q3-derived gamecode plugin.\n");
|
|
return false;
|
|
}
|
|
#ifndef STATIC_Q3
|
|
plugfuncs->ExportFunction("Tick", Q3_Frame);
|
|
#endif
|
|
|
|
#ifdef HAVE_CLIENT
|
|
drawfuncs = plugfuncs->GetEngineInterface(plug2dfuncs_name, sizeof(*drawfuncs));
|
|
scenefuncs = plugfuncs->GetEngineInterface(plug3dfuncs_name, sizeof(*scenefuncs));
|
|
inputfuncs = plugfuncs->GetEngineInterface(pluginputfuncs_name, sizeof(*inputfuncs));
|
|
clientfuncs = plugfuncs->GetEngineInterface(plugclientfuncs_name, sizeof(*clientfuncs));
|
|
audiofuncs = plugfuncs->GetEngineInterface(plugaudiofuncs_name, sizeof(*audiofuncs));
|
|
masterfuncs = plugfuncs->GetEngineInterface(plugmasterfuncs_name, sizeof(*masterfuncs));
|
|
if (drawfuncs && scenefuncs && inputfuncs && clientfuncs && audiofuncs && masterfuncs)
|
|
UI_Init();
|
|
#endif
|
|
return true;
|
|
}
|
|
#else
|
|
qboolean Plug_Init(void)
|
|
{
|
|
Con_Printf("Quake3 plugin without any support...\n");
|
|
return false;
|
|
}
|
|
#endif
|