diff --git a/engine/common/q3common.c b/engine/common/q3common.c new file mode 100644 index 000000000..8fe61b3e7 --- /dev/null +++ b/engine/common/q3common.c @@ -0,0 +1,1313 @@ +#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) + + + +#include "clq3defs.h" //okay, urr, this is bad for dedicated servers. urhum. Maybe they're not looking? It's only typedefs and one extern. + + + + + +/* +============ +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 +============ +*/ +static void MSG_WriteHuffBits( sizebuf_t *msg, int value, int bits ) +{ + int startbits; + int remaining; + int i; + + value &= 0xFFFFFFFFU >> (32 - bits); + remaining = bits & 7; + startbits = msg->currentbit; + + for( i=0; icurrentbit & 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; + +#ifdef MSG_PROFILING + msg_bitsEmitted += msg->currentbit - startbits; +#endif // MSG_PROFILING +} + +/* +============ +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; + case SZ_HUFFMAN: + MSG_WriteHuffBits( msg, value, bits ); + break; + } + +} + + + +//////////////////////////////////////////////////////////////////////////////// +//q3 netchan +//note that the sv and cl both have thier own wrappers, to handle encryption. + + + + + + + +#define MAX_PACKETLEN 1400 +#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; + + // Get sequence number + MSG_BeginReading(); + sequence = MSG_ReadBits(32); + + // 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_Printf("%s:Dropped %i packets at %i\n", NET_AdrToString(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(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(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_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; + + // 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 | FRAGMENT_MASK ); +#ifndef SERVERONLY + // Send the qport if we are a client + if( chan->sock == NS_CLIENT ) + { + MSG_WriteShort( &send, cls.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]; + + // Check for message overflow + if( length > MAX_OVERALLMSGLEN ) + { + Con_Printf( "%s: outgoing message overflow\n", NET_AdrToString( 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_Printf( "%s: unsent fragments\n", NET_AdrToString( chan->remote_address ) ); + 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; + } + + // 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, cls.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> 10) ^ key) >> 10) ^ key; +} + + + + + + + + + + + + + +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 +} field_t; + +// field declarations +#ifdef MSG_SHOWNET +# define PS_FIELD(n,b) { #n, ((int)&(((q3playerState_t *)0)->n)), b } +# define ES_FIELD(n,b) { #n, ((int)&(((q3entityState_t *)0)->n)), b } +#else +# define PS_FIELD(n,b) { ((int)&(((q3playerState_t *)0)->n)), b } +# define ES_FIELD(n,b) { ((int)&(((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< 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 field_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 ; iname, 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 field_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; inumber, 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; ibits) + { + 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 field_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 field_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 ; ibits ) + { + 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; istats[i] != to->stats[i]) + statsMask |= (1 << i); + } + + persistantMask = 0; + for(i=0 ; ipersistant[i] != to->persistant[i]) + persistantMask |= (1 << i); + } + + ammoMask = 0; + for(i=0 ; iammo[i] != to->ammo[i]) + ammoMask |= (1 << i); + } + + powerupsMask = 0; + for( i=0 ; ipowerups[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; istats[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; ipersistant[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; iammo[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; ipowerups[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 field_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 ; ibits ) + { + 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 ; istats[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 ; ipersistant[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 ; iammo[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 ; ipowerups[i] = MSG_ReadLong(); + } + } + } + } + +#ifdef MSG_SHOWNET + if( dump ) + { + Com_Printf( " (%i bits)\n", msg->bit - startbits ); + } +#endif +} +#endif + +//////////////////////////////////////////////////////////// +//user commands + +static int MSG_ReadDeltaKey(int key, int from, int bits) +{ + if (MSG_ReadBits(1)) + return MSG_ReadBits(bits)^key; + 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); + + if (!MSG_ReadBits(1)) + memcpy((qbyte *)to+4, (qbyte *)from+4, sizeof(usercmd_t)-4); + 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); + to->forwardmove = MSG_ReadDeltaKey(key, from->forwardmove, 8); + to->sidemove = MSG_ReadDeltaKey(key, from->sidemove, 8); + to->upmove = MSG_ReadDeltaKey(key, from->upmove, 8); + to->buttons = MSG_ReadDeltaKey(key, from->buttons, 16); + to->weapon = MSG_ReadDeltaKey(key, from->weapon, 8); + } +} +