/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "q_shared.h" #include "qcommon.h" /* ============================================================================== MESSAGE IO FUNCTIONS Handles byte ordering and avoids alignment errors ============================================================================== */ void MSG_Init( msg_t *buf, byte *data, int length ) { Com_Memset (buf, 0, sizeof(*buf)); buf->data = data; buf->maxsize = length; } void MSG_InitOOB( msg_t *buf, byte *data, int length ) { Com_Memset (buf, 0, sizeof(*buf)); buf->data = data; buf->maxsize = length; buf->oob = qtrue; } void MSG_Clear( msg_t *buf ) { buf->cursize = 0; buf->overflowed = qfalse; buf->bit = 0; //<- in bits } void MSG_Bitstream( msg_t *buf ) { buf->oob = qfalse; } void MSG_BeginReading( msg_t *msg ) { msg->readcount = 0; msg->bit = 0; msg->oob = qfalse; } void MSG_BeginReadingOOB( msg_t *msg ) { msg->readcount = 0; msg->bit = 0; msg->oob = qtrue; } void MSG_Copy( msg_t* buf, byte* data, int length, const msg_t* src ) { if (lengthcursize) { Com_Error( ERR_DROP, "MSG_Copy: can't copy into a smaller msg_t buffer"); } Com_Memcpy(buf, src, sizeof(msg_t)); buf->data = data; Com_Memcpy(buf->data, src->data, src->cursize); } /* ============================================================================= bit functions ============================================================================= */ //static int overflows; // negative bit values include signs void MSG_WriteBits( msg_t *msg, int value, int bits ) { int i, bitIndex; // this isn't an exact overflow check, but close enough if ( msg->maxsize - msg->cursize < 4 ) { msg->overflowed = qtrue; return; } if ( bits == 0 || bits < -31 || bits > 32 ) { Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits ); } if ( bits < 0 ) { bits = -bits; } if (msg->oob) { if (bits==8) { msg->data[msg->cursize] = value; msg->cursize += 1; msg->bit += 8; } else if (bits==16) { unsigned short *sp = (unsigned short *)&msg->data[msg->cursize]; *sp = LittleShort(value); msg->cursize += 2; msg->bit += 16; } else if (bits==32) { unsigned int *ip = (unsigned int *)&msg->data[msg->cursize]; *ip = LittleLong(value); msg->cursize += 4; msg->bit += 32; } else { Com_Error(ERR_DROP, "can't write %d bits\n", bits); } } else { value &= (0xffffffff>>(32-bits)); if (bits&7) { int nbits; nbits = bits&7; bitIndex = msg->bit; for(i=0;idata, bitIndex); value = (value>>1); bitIndex++; } msg->bit = bitIndex; bits = bits - nbits; } if (bits) { bitIndex = msg->bit; for(i=0;idata, bitIndex); value = (value>>8); } msg->bit = bitIndex; } msg->cursize = (msg->bit>>3)+1; } } int MSG_ReadBits( msg_t *msg, int bits ) { int value; int get; qbool sgn; int i, nbits, bitIndex; value = 0; if ( bits < 0 ) { bits = -bits; sgn = qtrue; } else { sgn = qfalse; } if (msg->oob) { if (bits==8) { value = msg->data[msg->readcount]; msg->readcount += 1; msg->bit += 8; } else if (bits==16) { unsigned short *sp = (unsigned short *)&msg->data[msg->readcount]; value = LittleShort(*sp); msg->readcount += 2; msg->bit += 16; } else if (bits==32) { unsigned int *ip = (unsigned int *)&msg->data[msg->readcount]; value = LittleLong(*ip); msg->readcount += 4; msg->bit += 32; } else { Com_Error(ERR_DROP_NDP, "can't read %d bits\n", bits); } } else { nbits = 0; if (bits&7) { nbits = bits&7; bitIndex = msg->bit; for(i=0;idata, bitIndex) << i; bitIndex++; } msg->bit = bitIndex; bits = bits - nbits; } if (bits) { bitIndex = msg->bit; for(i=0;idata, bitIndex); value |= (get<<(i+nbits)); } msg->bit = bitIndex; } msg->readcount = (msg->bit>>3)+1; } if ( sgn ) { if ( value & ( 1 << ( bits - 1 ) ) ) { value |= -1 ^ ( ( 1 << bits ) - 1 ); } } return value; } //================================================================================ // // writing functions // void MSG_WriteByte( msg_t *sb, int c ) { #ifdef PARANOID if (c < 0 || c > 255) Com_Error (ERR_FATAL, "MSG_WriteByte: range error"); #endif MSG_WriteBits( sb, c, 8 ); } void MSG_WriteData( msg_t *buf, const void *data, int length ) { int i; for(i=0;i (short)0x7fff) Com_Error (ERR_FATAL, "MSG_WriteShort: range error"); #endif MSG_WriteBits( sb, c, 16 ); } void MSG_WriteLong( msg_t *sb, int c ) { MSG_WriteBits( sb, c, 32 ); } void MSG_WriteString( msg_t *sb, const char *s ) { if ( !s ) { MSG_WriteData (sb, "", 1); } else { int l,i; char string[MAX_STRING_CHARS]; l = strlen( s ); if ( l >= MAX_STRING_CHARS ) { Com_Printf( "MSG_WriteString: MAX_STRING_CHARS" ); MSG_WriteData (sb, "", 1); return; } Q_strncpyz( string, s, sizeof( string ) ); // get rid of 0xff chars, because old clients don't like them for ( i = 0 ; i < l ; i++ ) { if ( ((byte *)string)[i] > 127 ) { string[i] = '.'; } } MSG_WriteData (sb, string, l+1); } } void MSG_WriteBigString( msg_t *sb, const char *s ) { if ( !s ) { MSG_WriteData (sb, "", 1); } else { int l,i; char string[BIG_INFO_STRING]; l = strlen( s ); if ( l >= BIG_INFO_STRING ) { Com_Printf( "MSG_WriteString: BIG_INFO_STRING" ); MSG_WriteData (sb, "", 1); return; } Q_strncpyz( string, s, sizeof( string ) ); // get rid of 0xff chars, because old clients don't like them for ( i = 0 ; i < l ; i++ ) { if ( ((byte *)string)[i] > 127 ) { string[i] = '.'; } } MSG_WriteData (sb, string, l+1); } } //============================================================ // // reading functions: return -1 if no more characters are available // int MSG_ReadByte( msg_t *msg ) { int c = (unsigned char)MSG_ReadBits( msg, 8 ); if ( msg->readcount > msg->cursize ) { c = -1; } return c; } int MSG_ReadShort( msg_t *msg ) { int c = (short)MSG_ReadBits( msg, 16 ); if ( msg->readcount > msg->cursize ) { c = -1; } return c; } int MSG_ReadLong( msg_t *msg ) { int c = MSG_ReadBits( msg, 32 ); if ( msg->readcount > msg->cursize ) { c = -1; } return c; } // // Notes about the MSG_Read*String* functions // // If N is the string buffer size, then: // - the max. string length is N-1 because // the NULL-terminator is part of the buffer // - the loop can call MSG_ReadByte at most N times // - the loop can write at most N-1 chars // to leave space for the NULL terminator // // The "q3msgboom" bug was happening because a string // of length >= N-1 was invoking MSG_ReadByte N-1 times // instead of N times, leaving the bit stream in an // invalid state and later leading to the // "Illegible server message" drop error. // char *MSG_ReadString( msg_t *msg ) { static char string[MAX_STRING_CHARS]; int l = 0; for (;;) { const int c = MSG_ReadByte(msg); if (c <= 0 || l >= sizeof(string) - 1) break; string[l++] = c == '%' || c > 127 ? '.' : c; } string[l] = '\0'; return string; } char *MSG_ReadBigString( msg_t *msg ) { static char string[BIG_INFO_STRING]; int l = 0; for (;;) { const int c = MSG_ReadByte(msg); if (c <= 0 || l >= sizeof(string) - 1) break; string[l++] = c == '%' || c > 127 ? '.' : c; } string[l] = '\0'; return string; } char *MSG_ReadStringLine( msg_t *msg ) { static char string[MAX_STRING_CHARS]; int l = 0; for (;;) { const int c = MSG_ReadByte(msg); if (c <= 0 || c == '\n' || l >= sizeof(string) - 1) break; string[l++] = c == '%' || c > 127 ? '.' : c; } string[l] = '\0'; return string; } void MSG_ReadData( msg_t *msg, void *data, int len ) { int i; for (i=0 ; iinteger == 4 ) { Com_Printf("%s ", x ); }; #else #define NETLOG(x) #endif static const int kbitmask[32] = { 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, int(0xFFFFFFFF), }; static void MSG_WriteDeltaKey( msg_t *msg, int key, int oldV, int newV, int bits ) { if ( oldV == newV ) { MSG_WriteBits( msg, 0, 1 ); return; } MSG_WriteBits( msg, 1, 1 ); MSG_WriteBits( msg, newV ^ key, bits ); } static int MSG_ReadDeltaKey( msg_t *msg, int key, int oldV, int bits ) { if ( MSG_ReadBits( msg, 1 ) ) { return MSG_ReadBits( msg, bits ) ^ (key & kbitmask[bits]); } return oldV; } /* ============================================================================ usercmd_t communication ucmds always have time, then either all fields or none ============================================================================ */ void MSG_WriteDeltaUsercmdKey( msg_t* msg, int key, const usercmd_t* from, usercmd_t* to ) { if ( to->serverTime - from->serverTime < 256 ) { MSG_WriteBits( msg, 1, 1 ); MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 ); } else { MSG_WriteBits( msg, 0, 1 ); MSG_WriteBits( msg, to->serverTime, 32 ); } if (from->angles[0] == to->angles[0] && from->angles[1] == to->angles[1] && from->angles[2] == to->angles[2] && from->forwardmove == to->forwardmove && from->rightmove == to->rightmove && from->upmove == to->upmove && from->buttons == to->buttons && from->weapon == to->weapon) { MSG_WriteBits( msg, 0, 1 ); // no change return; } key ^= to->serverTime; MSG_WriteBits( msg, 1, 1 ); MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 ); MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 ); MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 ); MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 ); MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 ); MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 ); MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 ); MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 ); } void MSG_ReadDeltaUsercmdKey( msg_t* msg, int key, const usercmd_t* from, usercmd_t* to ) { if ( MSG_ReadBits( msg, 1 ) ) { to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 ); } else { to->serverTime = MSG_ReadBits( msg, 32 ); } if ( MSG_ReadBits( msg, 1 ) ) { key ^= to->serverTime; to->angles[0] = MSG_ReadDeltaKey( msg, key, from->angles[0], 16); to->angles[1] = MSG_ReadDeltaKey( msg, key, from->angles[1], 16); to->angles[2] = MSG_ReadDeltaKey( msg, key, from->angles[2], 16); to->forwardmove = MSG_ReadDeltaKey( msg, key, from->forwardmove, 8); to->rightmove = MSG_ReadDeltaKey( msg, key, from->rightmove, 8); to->upmove = MSG_ReadDeltaKey( msg, key, from->upmove, 8); to->buttons = MSG_ReadDeltaKey( msg, key, from->buttons, 16); to->weapon = MSG_ReadDeltaKey( msg, key, from->weapon, 8); } else { to->angles[0] = from->angles[0]; to->angles[1] = from->angles[1]; to->angles[2] = from->angles[2]; to->forwardmove = from->forwardmove; to->rightmove = from->rightmove; to->upmove = from->upmove; to->buttons = from->buttons; to->weapon = from->weapon; } } /////////////////////////////////////////////////////////////// // if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS ) // the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent #define FLOAT_INT_BITS 13 #define FLOAT_INT_BIAS (1<<(FLOAT_INT_BITS-1)) typedef struct { const char* name; size_t offset; int bits; // 0 = float } netField_t; /* ============================================================================= entityState_t communication ============================================================================= */ // using the stringizing operator to save typing... #define ESF(x) #x,(size_t)&((entityState_t*)0)->x static const netField_t entityStateFields[] = { { ESF(pos.trTime), 32 }, { ESF(pos.trBase[0]), 0 }, { ESF(pos.trBase[1]), 0 }, { ESF(pos.trDelta[0]), 0 }, { ESF(pos.trDelta[1]), 0 }, { ESF(pos.trBase[2]), 0 }, { ESF(apos.trBase[1]), 0 }, { ESF(pos.trDelta[2]), 0 }, { ESF(apos.trBase[0]), 0 }, { ESF(event), 10 }, { ESF(angles2[1]), 0 }, { ESF(eType), 8 }, { ESF(torsoAnim), 8 }, { ESF(eventParm), 8 }, { ESF(legsAnim), 8 }, { ESF(groundEntityNum), GENTITYNUM_BITS }, { ESF(pos.trType), 8 }, { ESF(eFlags), 19 }, { ESF(otherEntityNum), GENTITYNUM_BITS }, { ESF(weapon), 8 }, { ESF(clientNum), 8 }, { ESF(angles[1]), 0 }, { ESF(pos.trDuration), 32 }, { ESF(apos.trType), 8 }, { ESF(origin[0]), 0 }, { ESF(origin[1]), 0 }, { ESF(origin[2]), 0 }, { ESF(solid), 24 }, { ESF(powerups), MAX_POWERUPS }, { ESF(modelindex), 8 }, { ESF(otherEntityNum2), GENTITYNUM_BITS }, { ESF(loopSound), 8 }, { ESF(generic1), 8 }, { ESF(origin2[2]), 0 }, { ESF(origin2[0]), 0 }, { ESF(origin2[1]), 0 }, { ESF(modelindex2), 8 }, { ESF(angles[0]), 0 }, { ESF(time), 32 }, { ESF(apos.trTime), 32 }, { ESF(apos.trDuration), 32 }, { ESF(apos.trBase[2]), 0 }, { ESF(apos.trDelta[0]), 0 }, { ESF(apos.trDelta[1]), 0 }, { ESF(apos.trDelta[2]), 0 }, { ESF(time2), 32 }, { ESF(angles[2]), 0 }, { ESF(angles2[0]), 0 }, { ESF(angles2[2]), 0 }, { ESF(constantLight), 32 }, { ESF(frame), 16 } }; static const int numESF = sizeof(entityStateFields) / sizeof(entityStateFields[0]); /* ================== MSG_WriteDeltaEntity Writes part of a packetentities message, including the entity number. Can delta from either a baseline or a previous packet_entity If to is NULL, a remove entity update will be sent If force is not set, then nothing at all will be generated if the entity is identical, under the assumption that the in-order delta code will catch it. ================== */ void MSG_WriteDeltaEntity( msg_t* msg, const entityState_t* from, const entityState_t* to, qbool force ) { int i, lc; int *fromF, *toF; // all fields should be 32 bits to avoid any compiler packing issues // the "number" field is not part of the field list // if this assert fails, someone added a field to the entityState_t // struct without updating the message fields COMPILE_TIME_ASSERT( numESF + 1 == sizeof(*from) / 4 ); // a NULL to is a delta remove message if ( to == NULL ) { if ( from == NULL ) { return; } MSG_WriteBits( msg, from->number, GENTITYNUM_BITS ); MSG_WriteBits( msg, 1, 1 ); return; } if ( to->number < 0 || to->number >= MAX_GENTITIES ) { Com_Error( ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number ); } lc = 0; const netField_t* field; // build the change vector as bytes so it is endien independent for ( i = 0, field = entityStateFields ; i < numESF ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); if ( *fromF != *toF ) { lc = i+1; } } if ( lc == 0 ) { // nothing at all changed if ( !force ) { return; // nothing at all } // write two bits for no change MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); MSG_WriteBits( msg, 0, 1 ); // not removed MSG_WriteBits( msg, 0, 1 ); // no delta return; } MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); MSG_WriteBits( msg, 0, 1 ); // not removed MSG_WriteBits( msg, 1, 1 ); // we have a delta MSG_WriteByte( msg, lc ); // # of changes for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); if ( *fromF == *toF ) { MSG_WriteBits( msg, 0, 1 ); // no change continue; } MSG_WriteBits( msg, 1, 1 ); // changed if ( field->bits == 0 ) { float fullFloat = *(float *)toF; if (fullFloat == 0.0f) { MSG_WriteBits( msg, 0, 1 ); } else { MSG_WriteBits( msg, 1, 1 ); int trunc = (int)fullFloat; if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { // send as small integer MSG_WriteBits( msg, 0, 1 ); MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); } else { // send as full floating point value MSG_WriteBits( msg, 1, 1 ); MSG_WriteBits( msg, *toF, 32 ); } } } else { if (*toF == 0) { MSG_WriteBits( msg, 0, 1 ); } else { MSG_WriteBits( msg, 1, 1 ); // integer MSG_WriteBits( msg, *toF, field->bits ); } } } } /* ================== MSG_ReadDeltaEntity The entity number has already been read from the message, which is how the from state is identified. If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1 Can go from either a baseline or a previous packet_entity ================== */ void MSG_ReadDeltaEntity( msg_t* msg, const entityState_t* from, entityState_t* to, int number ) { int i, lc; int *fromF, *toF; int print; int startBit, endBit; if ( number < 0 || number >= MAX_GENTITIES ) { Com_Error( ERR_DROP_NDP, "Bad delta entity number: %i", number ); } if ( msg->bit == 0 ) { startBit = msg->readcount * 8 - GENTITYNUM_BITS; } else { startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; } // check for a remove if ( MSG_ReadBits( msg, 1 ) == 1 ) { Com_Memset( to, 0, sizeof( *to ) ); to->number = MAX_GENTITIES - 1; #ifndef DEDICATED if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number ); } #endif return; } // check for no delta if ( MSG_ReadBits( msg, 1 ) == 0 ) { *to = *from; to->number = number; return; } lc = MSG_ReadByte(msg); if ( lc < 0 || lc > ARRAY_LEN(entityStateFields) ) { Com_Error( ERR_DROP_NDP, "invalid entityState_t field count %d (max: %d)\n", lc, ARRAY_LEN(entityStateFields) ); } // shownet 2/3 will interleave with other printed info, -1 will // just print the delta records #ifndef DEDICATED if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { print = 1; Com_Printf( "%3i: #%-3i ", msg->readcount, to->number ); } else { print = 0; } #else print = 0; #endif to->number = number; const netField_t* field; for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); if ( ! MSG_ReadBits( msg, 1 ) ) { // no change *toF = *fromF; } else { if ( field->bits == 0 ) { // float if ( MSG_ReadBits( msg, 1 ) == 0 ) { *(float *)toF = 0.0f; } else { if ( MSG_ReadBits( msg, 1 ) == 0 ) { // integral float int trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); // bias to allow equal parts positive and negative trunc -= FLOAT_INT_BIAS; *(float *)toF = trunc; if ( print ) { Com_Printf( "%s:%i ", field->name, trunc ); } } else { // full floating point value *toF = MSG_ReadBits( msg, 32 ); if ( print ) { Com_Printf( "%s:%f ", field->name, *(float *)toF ); } } } } else { if ( MSG_ReadBits( msg, 1 ) == 0 ) { *toF = 0; } else { // integer *toF = MSG_ReadBits( msg, field->bits ); if ( print ) { Com_Printf( "%s:%i ", field->name, *toF ); } } } // pcount[i]++; } } for ( i = lc, field = &entityStateFields[lc] ; i < numESF ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); // no change *toF = *fromF; } if ( print ) { if ( msg->bit == 0 ) { endBit = msg->readcount * 8 - GENTITYNUM_BITS; } else { endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; } Com_Printf( " (%i bits)\n", endBit - startBit ); } } /* ============================================================================ plyer_state_t communication ============================================================================ */ // using the stringizing operator to save typing... #define PSF(x) #x,(size_t)&((playerState_t*)0)->x static const netField_t playerStateFields[] = { { PSF(commandTime), 32 }, { PSF(origin[0]), 0 }, { PSF(origin[1]), 0 }, { PSF(bobCycle), 8 }, { PSF(velocity[0]), 0 }, { PSF(velocity[1]), 0 }, { PSF(viewangles[1]), 0 }, { PSF(viewangles[0]), 0 }, { PSF(weaponTime), -16 }, { PSF(origin[2]), 0 }, { PSF(velocity[2]), 0 }, { PSF(legsTimer), 8 }, { PSF(pm_time), -16 }, { PSF(eventSequence), 16 }, { PSF(torsoAnim), 8 }, { PSF(movementDir), 4 }, { PSF(events[0]), 8 }, { PSF(legsAnim), 8 }, { PSF(events[1]), 8 }, { PSF(pm_flags), 16 }, { PSF(groundEntityNum), GENTITYNUM_BITS }, { PSF(weaponstate), 4 }, { PSF(eFlags), 16 }, { PSF(externalEvent), 10 }, { PSF(gravity), 16 }, { PSF(speed), 16 }, { PSF(delta_angles[1]), 16 }, { PSF(externalEventParm), 8 }, { PSF(viewheight), -8 }, { PSF(damageEvent), 8 }, { PSF(damageYaw), 8 }, { PSF(damagePitch), 8 }, { PSF(damageCount), 8 }, { PSF(generic1), 8 }, { PSF(pm_type), 8 }, { PSF(delta_angles[0]), 16 }, { PSF(delta_angles[2]), 16 }, { PSF(torsoTimer), 12 }, { PSF(eventParms[0]), 8 }, { PSF(eventParms[1]), 8 }, { PSF(clientNum), 8 }, { PSF(weapon), 5 }, { PSF(viewangles[2]), 0 }, { PSF(grapplePoint[0]), 0 }, { PSF(grapplePoint[1]), 0 }, { PSF(grapplePoint[2]), 0 }, { PSF(jumppad_ent), 10 }, { PSF(loopSound), 16 } }; static const int numPSF = sizeof(playerStateFields) / sizeof(playerStateFields[0]); void MSG_WriteDeltaPlayerstate( msg_t* msg, const playerState_t* from, playerState_t* to ) { int i; playerState_t dummy; int c; int *fromF, *toF; int lc; if (!from) { from = &dummy; Com_Memset (&dummy, 0, sizeof(dummy)); } c = msg->cursize; lc = 0; const netField_t* field; for ( i = 0, field = playerStateFields ; i < numPSF ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); if ( *fromF != *toF ) { lc = i+1; } } MSG_WriteByte( msg, lc ); // # of changes for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); if ( *fromF == *toF ) { MSG_WriteBits( msg, 0, 1 ); // no change continue; } MSG_WriteBits( msg, 1, 1 ); // changed // pcount[i]++; if ( field->bits == 0 ) { float fullFloat = *(float *)toF; int trunc = (int)fullFloat; if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { // send as small integer MSG_WriteBits( msg, 0, 1 ); MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); } else { // send as full floating point value MSG_WriteBits( msg, 1, 1 ); MSG_WriteBits( msg, *toF, 32 ); } } else { // integer MSG_WriteBits( msg, *toF, field->bits ); } } c = msg->cursize - c; // // send the arrays // int statsbits = 0; for (i=0 ; istats[i] != from->stats[i]) { statsbits |= 1<persistant[i] != from->persistant[i]) { persistantbits |= 1<ammo[i] != from->ammo[i]) { ammobits |= 1<powerups[i] != from->powerups[i]) { powerupbits |= 1<stats[i]); } else { MSG_WriteBits( msg, 0, 1 ); // no change } if ( persistantbits ) { MSG_WriteBits( msg, 1, 1 ); // changed MSG_WriteBits( msg, persistantbits, MAX_PERSISTANT ); for (i=0 ; ipersistant[i]); } else { MSG_WriteBits( msg, 0, 1 ); // no change } if ( ammobits ) { MSG_WriteBits( msg, 1, 1 ); // changed MSG_WriteBits( msg, ammobits, MAX_WEAPONS ); for (i=0 ; iammo[i]); } else { MSG_WriteBits( msg, 0, 1 ); // no change } if ( powerupbits ) { MSG_WriteBits( msg, 1, 1 ); // changed MSG_WriteBits( msg, powerupbits, MAX_POWERUPS ); for (i=0 ; ipowerups[i] ); } else { MSG_WriteBits( msg, 0, 1 ); // no change } } void MSG_ReadDeltaPlayerstate( msg_t* msg, const playerState_t* from, playerState_t* to ) { int i, lc; int bits; int startBit, endBit; int print; int *fromF, *toF; int trunc; playerState_t dummy; if ( !from ) { from = &dummy; Com_Memset( &dummy, 0, sizeof( dummy ) ); } *to = *from; if ( msg->bit == 0 ) { startBit = msg->readcount * 8 - GENTITYNUM_BITS; } else { startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; } // shownet 2/3 will interleave with other printed info, -2 will // just print the delta records #ifndef DEDICATED if ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) { print = 1; Com_Printf( "%3i: playerstate ", msg->readcount ); } else { print = 0; } #else print = 0; #endif lc = MSG_ReadByte(msg); if ( lc < 0 || lc > ARRAY_LEN(playerStateFields) ) { Com_Error( ERR_DROP_NDP, "invalid playerState_t field count %d (max: %d)\n", lc, ARRAY_LEN(playerStateFields) ); } const netField_t* field; for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); if ( ! MSG_ReadBits( msg, 1 ) ) { // no change *toF = *fromF; } else { if ( field->bits == 0 ) { // float if ( MSG_ReadBits( msg, 1 ) == 0 ) { // integral float trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); // bias to allow equal parts positive and negative trunc -= FLOAT_INT_BIAS; *(float *)toF = trunc; if ( print ) { Com_Printf( "%s:%i ", field->name, trunc ); } } else { // full floating point value *toF = MSG_ReadBits( msg, 32 ); if ( print ) { Com_Printf( "%s:%f ", field->name, *(float *)toF ); } } } else { // integer *toF = MSG_ReadBits( msg, field->bits ); if ( print ) { Com_Printf( "%s:%i ", field->name, *toF ); } } } } for ( i = lc, field = &playerStateFields[lc] ; i < numPSF ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); // no change *toF = *fromF; } // read the arrays if (MSG_ReadBits( msg, 1 ) ) { // parse stats if ( MSG_ReadBits( msg, 1 ) ) { NETLOG("PS_STATS"); bits = MSG_ReadBits (msg, MAX_STATS); for (i=0 ; istats[i] = MSG_ReadShort(msg); } } } // parse persistant stats if ( MSG_ReadBits( msg, 1 ) ) { NETLOG("PS_PERSISTANT"); bits = MSG_ReadBits (msg, MAX_PERSISTANT); for (i=0 ; ipersistant[i] = MSG_ReadShort(msg); } } } // parse ammo if ( MSG_ReadBits( msg, 1 ) ) { NETLOG("PS_AMMO"); bits = MSG_ReadBits (msg, MAX_WEAPONS); for (i=0 ; iammo[i] = MSG_ReadShort(msg); } } } // parse powerups if ( MSG_ReadBits( msg, 1 ) ) { NETLOG("PS_POWERUPS"); bits = MSG_ReadBits (msg, MAX_POWERUPS); for (i=0 ; ipowerups[i] = MSG_ReadLong(msg); } } } } if ( print ) { if ( msg->bit == 0 ) { endBit = msg->readcount * 8 - GENTITYNUM_BITS; } else { endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; } Com_Printf( " (%i bits)\n", endBit - startBit ); } }