//======================================================================= // Copyright XashXT Group 2010 © // net_encode.c - encode network messages //======================================================================= #include "common.h" #include "netchan.h" #include "byteorder.h" #include "mathlib.h" #include "net_encode.h" #include "event_api.h" #include "usercmd.h" #include "pm_movevars.h" #include "entity_state.h" #include "weaponinfo.h" #include "entity_types.h" #include "protocol.h" #define DELTA_PATH "delta.lst" static bool delta_init = false; // list of all the struct names static const delta_field_t cmd_fields[] = { { UCMD_DEF(lerp_msec) }, { UCMD_DEF(msec) }, { UCMD_DEF(viewangles[0]) }, { UCMD_DEF(viewangles[1]) }, { UCMD_DEF(viewangles[2]) }, { UCMD_DEF(forwardmove) }, { UCMD_DEF(sidemove) }, { UCMD_DEF(upmove) }, { UCMD_DEF(lightlevel) }, { UCMD_DEF(buttons) }, { UCMD_DEF(impulse) }, { UCMD_DEF(weaponselect) }, { UCMD_DEF(impact_index) }, { UCMD_DEF(impact_position[0])}, { UCMD_DEF(impact_position[1])}, { UCMD_DEF(impact_position[2])}, { NULL }, }; static const delta_field_t pm_fields[] = { { PHYS_DEF( gravity ) }, { PHYS_DEF( stopspeed ) }, { PHYS_DEF( maxspeed ) }, { PHYS_DEF( spectatormaxspeed ) }, { PHYS_DEF( accelerate ) }, { PHYS_DEF( airaccelerate ) }, { PHYS_DEF( wateraccelerate ) }, { PHYS_DEF( friction ) }, { PHYS_DEF( edgefriction ) }, { PHYS_DEF( waterfriction ) }, { PHYS_DEF( bounce ) }, { PHYS_DEF( stepsize ) }, { PHYS_DEF( maxvelocity ) }, { PHYS_DEF( zmax ) }, { PHYS_DEF( waveHeight ) }, { PHYS_DEF( footsteps ) }, { PHYS_DEF( skyName ) }, { PHYS_DEF( rollangle ) }, { PHYS_DEF( rollspeed ) }, { PHYS_DEF( skycolor_r ) }, { PHYS_DEF( skycolor_g ) }, { PHYS_DEF( skycolor_b ) }, { PHYS_DEF( skyvec_x ) }, { PHYS_DEF( skyvec_y ) }, { PHYS_DEF( skyvec_z ) }, { NULL }, }; static const delta_field_t ev_fields[] = { { EVNT_DEF( flags ) }, { EVNT_DEF( entindex ) }, { EVNT_DEF( origin[0] ) }, { EVNT_DEF( origin[1] ) }, { EVNT_DEF( origin[2] ) }, { EVNT_DEF( angles[0] ) }, { EVNT_DEF( angles[1] ) }, { EVNT_DEF( angles[2] ) }, { EVNT_DEF( velocity[0] ) }, { EVNT_DEF( velocity[1] ) }, { EVNT_DEF( velocity[2] ) }, { EVNT_DEF( ducking ) }, { EVNT_DEF( fparam1 ) }, { EVNT_DEF( fparam2 ) }, { EVNT_DEF( iparam1 ) }, { EVNT_DEF( iparam2 ) }, { EVNT_DEF( bparam1 ) }, { EVNT_DEF( bparam2 ) }, { NULL }, }; static const delta_field_t wd_fields[] = { { WPDT_DEF( m_iId ) }, { WPDT_DEF( m_iClip ) }, { WPDT_DEF( m_flNextPrimaryAttack ) }, { WPDT_DEF( m_flNextSecondaryAttack ) }, { WPDT_DEF( m_flTimeWeaponIdle ) }, { WPDT_DEF( m_fInReload ) }, { WPDT_DEF( m_fInSpecialReload ) }, { WPDT_DEF( m_flNextReload ) }, { WPDT_DEF( m_flPumpTime ) }, { WPDT_DEF( m_fReloadTime ) }, { WPDT_DEF( m_fAimedDamage ) }, { WPDT_DEF( m_fNextAimBonus ) }, { WPDT_DEF( m_fInZoom ) }, { WPDT_DEF( m_iWeaponState ) }, { WPDT_DEF( iuser1 ) }, { WPDT_DEF( iuser2 ) }, { WPDT_DEF( iuser3 ) }, { WPDT_DEF( iuser4 ) }, { WPDT_DEF( fuser1 ) }, { WPDT_DEF( fuser2 ) }, { WPDT_DEF( fuser3 ) }, { WPDT_DEF( fuser4 ) }, { NULL }, }; static const delta_field_t cd_fields[] = { { CLDT_DEF( origin[0] ) }, { CLDT_DEF( origin[1] ) }, { CLDT_DEF( origin[2] ) }, { CLDT_DEF( velocity[0] ) }, { CLDT_DEF( velocity[1] ) }, { CLDT_DEF( velocity[2] ) }, { CLDT_DEF( viewmodel ) }, { CLDT_DEF( punchangle[0] ) }, { CLDT_DEF( punchangle[1] ) }, { CLDT_DEF( punchangle[2] ) }, { CLDT_DEF( flags ) }, { CLDT_DEF( waterlevel ) }, { CLDT_DEF( watertype ) }, { CLDT_DEF( view_ofs[0] ) }, { CLDT_DEF( view_ofs[1] ) }, { CLDT_DEF( view_ofs[2] ) }, { CLDT_DEF( health ) }, { CLDT_DEF( bInDuck ) }, { CLDT_DEF( weapons ) }, { CLDT_DEF( flTimeStepSound ) }, { CLDT_DEF( flDuckTime ) }, { CLDT_DEF( flSwimTime ) }, { CLDT_DEF( waterjumptime ) }, { CLDT_DEF( maxspeed ) }, { CLDT_DEF( fov ) }, { CLDT_DEF( weaponanim ) }, { CLDT_DEF( m_iId ) }, { CLDT_DEF( ammo_shells ) }, { CLDT_DEF( ammo_nails ) }, { CLDT_DEF( ammo_cells ) }, { CLDT_DEF( ammo_rockets ) }, { CLDT_DEF( m_flNextAttack ) }, { CLDT_DEF( tfstate ) }, { CLDT_DEF( pushmsec ) }, { CLDT_DEF( deadflag ) }, { CLDT_DEF( physinfo ) }, { CLDT_DEF( iuser1 ) }, { CLDT_DEF( iuser2 ) }, { CLDT_DEF( iuser3 ) }, { CLDT_DEF( iuser4 ) }, { CLDT_DEF( fuser1 ) }, { CLDT_DEF( fuser2 ) }, { CLDT_DEF( fuser3 ) }, { CLDT_DEF( fuser4 ) }, { CLDT_DEF( vuser1[0] ) }, { CLDT_DEF( vuser1[1] ) }, { CLDT_DEF( vuser1[2] ) }, { CLDT_DEF( vuser2[0] ) }, { CLDT_DEF( vuser2[1] ) }, { CLDT_DEF( vuser2[2] ) }, { CLDT_DEF( vuser3[0] ) }, { CLDT_DEF( vuser3[1] ) }, { CLDT_DEF( vuser3[2] ) }, { CLDT_DEF( vuser4[0] ) }, { CLDT_DEF( vuser4[1] ) }, { CLDT_DEF( vuser4[2] ) }, { NULL }, }; static const delta_field_t ent_fields[] = { { ENTS_DEF( entityType ) }, { ENTS_DEF( origin[0] ) }, { ENTS_DEF( origin[1] ) }, { ENTS_DEF( origin[2] ) }, { ENTS_DEF( angles[0] ) }, { ENTS_DEF( angles[1] ) }, { ENTS_DEF( angles[2] ) }, { ENTS_DEF( modelindex ) }, { ENTS_DEF( sequence ) }, { ENTS_DEF( frame ) }, { ENTS_DEF( colormap ) }, { ENTS_DEF( skin ) }, { ENTS_DEF( solid ) }, { ENTS_DEF( effects ) }, { ENTS_DEF( scale ) }, { ENTS_DEF( eflags ) }, { ENTS_DEF( rendermode ) }, { ENTS_DEF( renderamt ) }, { ENTS_DEF( rendercolor.r ) }, { ENTS_DEF( rendercolor.g ) }, { ENTS_DEF( rendercolor.b ) }, { ENTS_DEF( renderfx ) }, { ENTS_DEF( movetype ) }, { ENTS_DEF( animtime ) }, { ENTS_DEF( framerate ) }, { ENTS_DEF( body ) }, { ENTS_DEF( controller[0] ) }, { ENTS_DEF( controller[1] ) }, { ENTS_DEF( controller[2] ) }, { ENTS_DEF( controller[3] ) }, { ENTS_DEF( blending[0] ) }, { ENTS_DEF( blending[1] ) }, { ENTS_DEF( blending[2] ) }, { ENTS_DEF( blending[3] ) }, { ENTS_DEF( velocity[0] ) }, { ENTS_DEF( velocity[1] ) }, { ENTS_DEF( velocity[2] ) }, { ENTS_DEF( mins[0] ) }, { ENTS_DEF( mins[1] ) }, { ENTS_DEF( mins[2] ) }, { ENTS_DEF( maxs[0] ) }, { ENTS_DEF( maxs[1] ) }, { ENTS_DEF( maxs[2] ) }, { ENTS_DEF( aiment ) }, { ENTS_DEF( owner ) }, { ENTS_DEF( friction ) }, { ENTS_DEF( gravity ) }, { ENTS_DEF( team ) }, { ENTS_DEF( playerclass ) }, { ENTS_DEF( health ) }, { ENTS_DEF( spectator ) }, { ENTS_DEF( weaponmodel ) }, { ENTS_DEF( gaitsequence ) }, { ENTS_DEF( basevelocity[0] ) }, { ENTS_DEF( basevelocity[1] ) }, { ENTS_DEF( basevelocity[2] ) }, { ENTS_DEF( usehull ) }, { ENTS_DEF( oldbuttons ) }, // probably never transmitted { ENTS_DEF( onground ) }, { ENTS_DEF( iStepLeft ) }, { ENTS_DEF( flFallVelocity ) }, { ENTS_DEF( fov ) }, { ENTS_DEF( weaponanim ) }, { ENTS_DEF( startpos[0] ) }, { ENTS_DEF( startpos[1] ) }, { ENTS_DEF( startpos[2] ) }, { ENTS_DEF( endpos[0] ) }, { ENTS_DEF( endpos[1] ) }, { ENTS_DEF( endpos[2] ) }, { ENTS_DEF( impacttime ) }, { ENTS_DEF( starttime ) }, { ENTS_DEF( iuser1 ) }, { ENTS_DEF( iuser2 ) }, { ENTS_DEF( iuser3 ) }, { ENTS_DEF( iuser4 ) }, { ENTS_DEF( fuser1 ) }, { ENTS_DEF( fuser2 ) }, { ENTS_DEF( fuser3 ) }, { ENTS_DEF( fuser4 ) }, { ENTS_DEF( vuser1[0] ) }, { ENTS_DEF( vuser1[1] ) }, { ENTS_DEF( vuser1[2] ) }, { ENTS_DEF( vuser2[0] ) }, { ENTS_DEF( vuser2[1] ) }, { ENTS_DEF( vuser2[2] ) }, { ENTS_DEF( vuser3[0] ) }, { ENTS_DEF( vuser3[1] ) }, { ENTS_DEF( vuser3[2] ) }, { ENTS_DEF( vuser4[0] ) }, { ENTS_DEF( vuser4[1] ) }, { ENTS_DEF( vuser4[2] ) }, { ENTS_DEF( classname ) }, { ENTS_DEF( targetname ) }, { ENTS_DEF( target ) }, { ENTS_DEF( netname ) }, { NULL }, }; static delta_info_t dt_info[] = { { "event_t", ev_fields, NUM_FIELDS( ev_fields ) }, { "movevars_t", pm_fields, NUM_FIELDS( pm_fields ) }, { "usercmd_t", cmd_fields, NUM_FIELDS( cmd_fields ) }, { "clientdata_t", cd_fields, NUM_FIELDS( cd_fields ) }, { "weapon_data_t", wd_fields, NUM_FIELDS( wd_fields ) }, { "entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) }, { "entity_state_player_t", ent_fields, NUM_FIELDS( ent_fields ) }, { "custom_entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) }, { NULL }, }; delta_info_t *Delta_FindStruct( const char *name ) { int i; if( !name || !name[0] ) return NULL; for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) { if( !com.stricmp( dt_info[i].pName, name )) return &dt_info[i]; } MsgDev( D_WARN, "Struct %s not found in delta_info\n", name ); // found nothing return NULL; } int Delta_NumTables( void ) { return NUM_FIELDS( dt_info ); } delta_info_t *Delta_FindStructByIndex( int index ) { if( index < 0 || index >= NUM_FIELDS( dt_info )) return NULL; return &dt_info[index]; } delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) { int i; if( !encoderName || !encoderName[0] ) return NULL; for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) { if( !com.stricmp( dt_info[i].funcName, encoderName )) return &dt_info[i]; } // found nothing return NULL; } delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) { int i; if( !pFields ) return NULL; for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) { if( dt_info[i].pFields == pFields ) return &dt_info[i]; } // found nothing return NULL; } void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) { int i; ASSERT( dt != NULL ); // set all fields is active by default for( i = 0; i < dt->numFields; i++ ) dt->pFields[i].bInactive = false; if( dt->userCallback ) { dt->userCallback( dt->pFields, from, to ); } } delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName ) { if( !fieldName || !*fieldName ) return NULL; for( ; pInfo->name; pInfo++ ) { if( !com.strcmp( pInfo->name, fieldName )) return (delta_field_t *)pInfo; } return NULL; } int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName ) { int i; if( !fieldName || !*fieldName ) return -1; for( i = 0; pInfo->name; i++, pInfo++ ) { if( !com.strcmp( pInfo->name, fieldName )) return i; } return -1; } bool Delta_AddField( const char *pStructName, const char *pName, int flags, int bits, float mul, float post_mul ) { delta_info_t *dt; delta_field_t *pFieldInfo; delta_t *pField; int i; // get the delta struct dt = Delta_FindStruct( pStructName ); ASSERT( dt != NULL ); // check for coexisting field for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) { if( !com.strcmp( pField->name, pName )) { MsgDev( D_ERROR, "Delta_Add: %s->%s already existing\n", pStructName, pName ); return false; // field already exist } } // find field description pFieldInfo = Delta_FindFieldInfo( dt->pInfo, pName ); if( !pFieldInfo ) { MsgDev( D_ERROR, "Delta_Add: couldn't find description for %s->%s\n", pStructName, pName ); return false; } if( dt->numFields + 1 > dt->maxFields ) { MsgDev( D_WARN, "Delta_Add: can't add %s->%s encoder list is full\n", pStructName, pName ); return false; // too many fields specified (duplicated ?) } // allocate a new one dt->pFields = Z_Realloc( dt->pFields, (dt->numFields + 1) * sizeof( delta_t )); for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ); // copy info to new field pField->name = pFieldInfo->name; pField->offset = pFieldInfo->offset; pField->size = pFieldInfo->size; pField->flags = flags; pField->bits = bits; pField->multiplier = mul; pField->post_multiplier = post_mul; dt->numFields++; return true; } void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField ) { int nameIndex; delta_info_t *dt; ASSERT( pField ); if( !pField->name || !*pField->name ) return; // not initialized ? dt = Delta_FindStructByIndex( tableIndex ); ASSERT( dt && dt->bInitialized ); nameIndex = Delta_IndexForFieldInfo( dt->pInfo, pField->name ); ASSERT( nameIndex >= 0 && nameIndex < dt->maxFields ); BF_WriteByte( msg, svc_deltatable ); BF_WriteUBitLong( msg, tableIndex, 4 ); // assume we support 16 network tables BF_WriteUBitLong( msg, nameIndex, 8 ); // 255 fields by struct should be enough BF_WriteUBitLong( msg, pField->flags, 8 ); // flags is full, expand to 10 bits ? BF_WriteUBitLong( msg, pField->bits - 1, 5 ); // max received value is 32 (32 bit) // multipliers is null-compressed if( pField->multiplier != 1.0f ) { BF_WriteOneBit( msg, 1 ); BF_WriteFloat( msg, pField->multiplier ); } else BF_WriteOneBit( msg, 0 ); if( pField->post_multiplier != 1.0f ) { BF_WriteOneBit( msg, 1 ); BF_WriteFloat( msg, pField->post_multiplier ); } else BF_WriteOneBit( msg, 0 ); } void Delta_ParseTableField( sizebuf_t *msg ) { int tableIndex, nameIndex; float mul = 1.0f, post_mul = 1.0f; int flags, bits; const char *pName; delta_info_t *dt; tableIndex = BF_ReadUBitLong( msg, 4 ); dt = Delta_FindStructByIndex( tableIndex ); ASSERT( dt != NULL ); nameIndex = BF_ReadUBitLong( msg, 8 ); // read field name index ASSERT( nameIndex >= 0 && nameIndex < dt->maxFields ); pName = dt->pInfo[nameIndex].name; flags = BF_ReadUBitLong( msg, 8 ); bits = BF_ReadUBitLong( msg, 5 ) + 1; // read the multipliers if( BF_ReadOneBit( msg )) mul = BF_ReadFloat( msg ); if( BF_ReadOneBit( msg )) post_mul = BF_ReadFloat( msg ); // delta encoders it's already initialized on this machine if( delta_init ) return; // add field to table Delta_AddField( dt->pName, pName, flags, bits, mul, post_mul ); } bool Delta_ParseField( script_t *delta_script, const delta_field_t *pInfo, delta_t *pField, bool bPost ) { token_t token; delta_field_t *pFieldInfo; Com_ReadToken( delta_script, 0, &token ); if( com.strcmp( token.string, "(" )) { MsgDev( D_ERROR, "Delta_ParseField: expected '(', found '%s' instead\n", token.string ); return false; } // read the variable name if( !Com_ReadToken( delta_script, SC_ALLOW_PATHNAMES2, &token )) { MsgDev( D_ERROR, "Delta_ParseField: missing field name\n" ); return false; } pFieldInfo = Delta_FindFieldInfo( pInfo, token.string ); if( !pFieldInfo ) { MsgDev( D_ERROR, "Delta_ParseField: unable to find field %s\n", token.string ); return false; } Com_ReadToken( delta_script, 0, &token ); if( com.strcmp( token.string, "," )) { MsgDev( D_ERROR, "Delta_ParseField: expected ',', found '%s' instead\n", token.string ); return false; } // copy base info to new field pField->name = pFieldInfo->name; pField->offset = pFieldInfo->offset; pField->size = pFieldInfo->size; pField->flags = 0; // read delta-flags while( Com_ReadToken( delta_script, false, &token )) { if( !com.strcmp( token.string, "," )) break; // end of flags argument if( !com.strcmp( token.string, "|" )) continue; if( !com.strcmp( token.string, "DT_BYTE" )) pField->flags |= DT_BYTE; else if( !com.strcmp( token.string, "DT_SHORT" )) pField->flags |= DT_SHORT; else if( !com.strcmp( token.string, "DT_FLOAT" )) pField->flags |= DT_FLOAT; else if( !com.strcmp( token.string, "DT_INTEGER" )) pField->flags |= DT_INTEGER; else if( !com.strcmp( token.string, "DT_ANGLE" )) pField->flags |= DT_ANGLE; else if( !com.strncmp( token.string, "DT_TIMEWINDOW", 13 )) pField->flags |= DT_TIMEWINDOW; else if( !com.strcmp( token.string, "DT_STRING" )) pField->flags |= DT_STRING; else if( !com.strcmp( token.string, "DT_SIGNED" )) pField->flags |= DT_SIGNED; } if( com.strcmp( token.string, "," )) { MsgDev( D_ERROR, "Delta_ParseField: expected ',', found '%s' instead\n", token.string ); return false; } // read delta-bits if( !Com_ReadLong( delta_script, false, &pField->bits )) { MsgDev( D_ERROR, "Delta_ReadField: %s field bits argument is missing\n", pField->name ); return false; } Com_ReadToken( delta_script, 0, &token ); if( com.strcmp( token.string, "," )) { MsgDev( D_ERROR, "Delta_ReadField: expected ',', found '%s' instead\n", token.string ); return false; } // read delta-multiplier if( !Com_ReadFloat( delta_script, false, &pField->multiplier )) { MsgDev( D_ERROR, "Delta_ReadField: %s missing 'multiplier' argument\n", pField->name ); return false; } if( bPost ) { Com_ReadToken( delta_script, 0, &token ); if( com.strcmp( token.string, "," )) { MsgDev( D_ERROR, "Delta_ReadField: expected ',', found '%s' instead\n", token.string ); return false; } // read delta-postmultiplier if( !Com_ReadFloat( delta_script, false, &pField->post_multiplier )) { MsgDev( D_ERROR, "Delta_ReadField: %s missing 'post_multiply' argument\n", pField->name ); return false; } } else { // to avoid division by zero pField->post_multiplier = 1.0f; } // closing brace... Com_ReadToken( delta_script, 0, &token ); if( com.strcmp( token.string, ")" )) { MsgDev( D_ERROR, "Delta_ParseField: expected ')', found '%s' instead\n", token.string ); return false; } // ... and trying to parse optional ',' post-symbol Com_ReadToken( delta_script, 0, &token ); return true; } void Delta_ParseTable( script_t *delta_script, delta_info_t *dt, const char *encodeDll, const char *encodeFunc ) { token_t token; delta_t *pField; const delta_field_t *pInfo; // allocate the delta-structures if( !dt->pFields ) dt->pFields = (delta_t *)Z_Malloc( dt->maxFields * sizeof( delta_t )); pField = dt->pFields; pInfo = dt->pInfo; dt->numFields = 0; // assume we have handled '{' while( Com_ReadToken( delta_script, SC_ALLOW_NEWLINES|SC_ALLOW_PATHNAMES2, &token )) { ASSERT( dt->numFields <= dt->maxFields ); if( !com.strcmp( token.string, "DEFINE_DELTA" )) { if( Delta_ParseField( delta_script, pInfo, &pField[dt->numFields], false )) dt->numFields++; else Com_SkipRestOfLine( delta_script ); } else if( !com.strcmp( token.string, "DEFINE_DELTA_POST" )) { if( Delta_ParseField( delta_script, pInfo, &pField[dt->numFields], true )) dt->numFields++; else Com_SkipRestOfLine( delta_script ); } else if( token.string[0] == '}' ) { // end of the section break; } } // copy function name com.strncpy( dt->funcName, encodeFunc, sizeof( dt->funcName )); if( !com.stricmp( encodeDll, "none" )) dt->customEncode = CUSTOM_NONE; else if( !com.stricmp( encodeDll, "gamedll" )) dt->customEncode = CUSTOM_SEREVR_ENCODE; else if( !com.stricmp( encodeDll, "clientdll" )) dt->customEncode = CUSTOM_CLIENT_ENCODE; // adjust to fit memory size if( dt->numFields < dt->maxFields ) { dt->pFields = Z_Realloc( dt->pFields, dt->numFields * sizeof( delta_t )); } dt->bInitialized = true; // table is ok } void Delta_InitFields( void ) { script_t *delta_script; delta_info_t *dt; string encodeDll, encodeFunc; token_t token; delta_script = Com_OpenScript( DELTA_PATH, NULL, 0 ); if( !delta_script ) { static string errormsg; com.snprintf( errormsg, sizeof( errormsg ), "DELTA_Load: couldn't load file %s\n", DELTA_PATH ); SV_SysError( errormsg ); com.error( errormsg ); } while( Com_ReadToken( delta_script, SC_ALLOW_NEWLINES|SC_ALLOW_PATHNAMES2, &token )) { dt = Delta_FindStruct( token.string ); Com_ReadString( delta_script, false, encodeDll ); if( !com.stricmp( encodeDll, "none" )) com.strcpy( encodeFunc, "null" ); else Com_ReadString( delta_script, false, encodeFunc ); // jump to '{' Com_ReadToken( delta_script, SC_ALLOW_NEWLINES, &token ); ASSERT( token.string[0] == '{' ); if( dt ) Delta_ParseTable( delta_script, dt, encodeDll, encodeFunc ); else Com_SkipBracedSection( delta_script, 1 ); } Com_CloseScript( delta_script ); // adding some requrid fields fields that use may forget or don't know how to specified Delta_AddField( "event_t", "flags", DT_INTEGER, 8, 1.0f, 1.0f ); } void Delta_Init( void ) { delta_info_t *dt; // shutdown it first if( delta_init ) Delta_Shutdown (); Delta_InitFields (); // initialize fields delta_init = true; dt = Delta_FindStruct( "movevars_t" ); ASSERT( dt != NULL ); if( dt->bInitialized ) return; // specified by user // create movevars_t delta Delta_AddField( "movevars_t", "gravity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "stopspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "maxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "spectatormaxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "accelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "airaccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "wateraccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "friction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "edgefriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "waterfriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "bounce", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "stepsize", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); Delta_AddField( "movevars_t", "maxvelocity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "zmax", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); // no fractional part Delta_AddField( "movevars_t", "waveHeight", DT_FLOAT|DT_SIGNED, 16, 1.0f, 16.0f ); Delta_AddField( "movevars_t", "skyName", DT_STRING, 1, 1.0f, 1.0f ); Delta_AddField( "movevars_t", "footsteps", DT_INTEGER, 1, 1.0f, 1.0f ); Delta_AddField( "movevars_t", "rollangle", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); Delta_AddField( "movevars_t", "rollspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); Delta_AddField( "movevars_t", "skycolor_r", DT_FLOAT|DT_SIGNED, 9, 1.0f, 1.0f ); // 0 - 264 Delta_AddField( "movevars_t", "skycolor_g", DT_FLOAT|DT_SIGNED, 9, 1.0f, 1.0f ); Delta_AddField( "movevars_t", "skycolor_b", DT_FLOAT|DT_SIGNED, 9, 1.0f, 1.0f ); Delta_AddField( "movevars_t", "skyvec_x", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); // 0 - 1 Delta_AddField( "movevars_t", "skyvec_y", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); Delta_AddField( "movevars_t", "skyvec_z", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); // now done dt->bInitialized = true; } void Delta_InitClient( void ) { int i, numActive = 0; // already initalized if( delta_init ) return; for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) { if( dt_info[i].numFields > 0 ) { dt_info[i].bInitialized = true; numActive++; } } if( numActive ) delta_init = true; } void Delta_Shutdown( void ) { int i; if( !delta_init ) return; for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) { dt_info[i].numFields = 0; dt_info[i].customEncode = CUSTOM_NONE; dt_info[i].userCallback = NULL; dt_info[i].funcName[0] = '\0'; if( dt_info[i].pFields ) { Z_Free( dt_info[i].pFields ); dt_info[i].pFields = NULL; } dt_info[i].bInitialized = false; } delta_init = false; } /* ===================== Delta_CompareField compare fields by offsets assume from and to is valid ===================== */ bool Delta_CompareField( delta_t *pField, void *from, void *to ) { int fromF, toF; ASSERT( pField ); ASSERT( from ); ASSERT( to ); if( pField->bInactive ) return true; fromF = toF = 0; if( pField->flags & DT_BYTE ) { if( pField->flags & DT_SIGNED ) { fromF = *(signed char *)((byte *)from + pField->offset ); toF = *(signed char *)((byte *)to + pField->offset ); } else { fromF = *(byte *)((byte *)from + pField->offset ); toF = *(byte *)((byte *)to + pField->offset ); } } else if( pField->flags & DT_SHORT ) { if( pField->flags & DT_SIGNED ) { fromF = *(short *)((byte *)from + pField->offset ); toF = *(short *)((byte *)to + pField->offset ); } else { fromF = *(word *)((byte *)from + pField->offset ); toF = *(word *)((byte *)to + pField->offset ); } } else if( pField->flags & DT_INTEGER ) { if( pField->flags & DT_SIGNED ) { fromF = *(int *)((byte *)from + pField->offset ); toF = *(int *)((byte *)to + pField->offset ); } else { fromF = *(uint *)((byte *)from + pField->offset ); toF = *(uint *)((byte *)to + pField->offset ); } } else if( pField->flags & ( DT_FLOAT|DT_ANGLE|DT_TIMEWINDOW )) { // don't convert floats to integers fromF = *((int *)((byte *)from + pField->offset )); toF = *((int *)((byte *)to + pField->offset )); } else if( pField->flags & DT_STRING ) { // compare strings char *s1 = (char *)((byte *)from + pField->offset ); char *s2 = (char *)((byte *)to + pField->offset ); // 0 is equal, otherwise not equal toF = com.strcmp( s1, s2 ); } return ( fromF == toF ) ? true : false; } /* ===================== Delta_WriteField write fields by offsets assume from and to is valid ===================== */ bool Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to, float timebase ) { bool bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float flValue, flAngle, flTime; uint iValue; const char *pStr; if( Delta_CompareField( pField, from, to )) { BF_WriteOneBit( msg, 0 ); // unchanged return false; } BF_WriteOneBit( msg, 1 ); // changed if( pField->flags & DT_BYTE ) { iValue = *(byte *)((byte *)to + pField->offset ); iValue *= pField->multiplier; BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_SHORT ) { iValue = *(word *)((byte *)to + pField->offset ); iValue *= pField->multiplier; BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_INTEGER ) { iValue = *(uint *)((byte *)to + pField->offset ); iValue *= pField->multiplier; BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_FLOAT ) { flValue = *(float *)((byte *)to + pField->offset ); iValue = (int)(flValue * pField->multiplier); BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_ANGLE ) { flAngle = *(float *)((byte *)to + pField->offset ); // NOTE: never applies multipliers to angle because // result may be wrong on client-side BF_WriteBitAngle( msg, flAngle, pField->bits ); } else if( pField->flags & DT_TIMEWINDOW ) { flValue = *(float *)((byte *)to + pField->offset ); flTime = ( timebase - flValue ); iValue = (uint)( flTime * 1000 ); BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_STRING ) { pStr = (char *)((byte *)to + pField->offset ); BF_WriteString( msg, pStr ); } return true; } /* ===================== Delta_ReadField read fields by offsets assume from and to is valid ===================== */ bool Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, float timebase ) { bool bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float flValue, flAngle, flTime; bool bChanged; uint iValue; const char *pStr; char *pOut; bChanged = BF_ReadOneBit( msg ); ASSERT( pField->multiplier != 0.0f ); if( pField->flags & DT_BYTE ) { if( bChanged ) { iValue = BF_ReadBitLong( msg, pField->bits, bSigned ); iValue /= pField->multiplier; } else { iValue = *(byte *)((byte *)from + pField->offset ); } *(byte *)((byte *)to + pField->offset ) = iValue; } else if( pField->flags & DT_SHORT ) { if( bChanged ) { iValue = BF_ReadBitLong( msg, pField->bits, bSigned ); iValue /= pField->multiplier; } else { iValue = *(word *)((byte *)from + pField->offset ); } *(word *)((byte *)to + pField->offset ) = iValue; } else if( pField->flags & DT_INTEGER ) { if( bChanged ) { iValue = BF_ReadBitLong( msg, pField->bits, bSigned ); iValue /= pField->multiplier; } else { iValue = *(uint *)((byte *)from + pField->offset ); } *(uint *)((byte *)to + pField->offset ) = iValue; } else if( pField->flags & DT_FLOAT ) { if( bChanged ) { iValue = BF_ReadBitLong( msg, pField->bits, bSigned ); flValue = (int)iValue * ( 1.0f / pField->multiplier ); flValue = flValue * pField->post_multiplier; } else { flValue = *(float *)((byte *)from + pField->offset ); } *(float *)((byte *)to + pField->offset ) = flValue; } else if( pField->flags & DT_ANGLE ) { if( bChanged ) { flAngle = BF_ReadBitAngle( msg, pField->bits ); } else { flAngle = *(float *)((byte *)from + pField->offset ); } *(float *)((byte *)to + pField->offset ) = flAngle; } else if( pField->flags & DT_TIMEWINDOW ) { if( bChanged ) { iValue = BF_ReadBitLong( msg, pField->bits, bSigned ); flValue = (float)((int)(iValue * 0.001f )); flTime = timebase + flValue; } else { flTime = *(float *)((byte *)from + pField->offset ); } *(float *)((byte *)to + pField->offset ) = flTime; } else if( pField->flags & DT_STRING ) { if( bChanged ) { pStr = BF_ReadString( msg ); } else { pStr = (char *)((byte *)from + pField->offset ); } pOut = (char *)((byte *)to + pField->offset ); com.strncpy( pOut, pStr, pField->size ); } return bChanged; } /* ============================================================================= usercmd_t communication ============================================================================= */ /* ===================== MSG_WriteDeltaUsercmd ===================== */ void MSG_WriteDeltaUsercmd( sizebuf_t *msg, usercmd_t *from, usercmd_t *to ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "usercmd_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); // activate fields and call custom encode func Delta_CustomEncode( dt, from, to ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_WriteField( msg, pField, from, to, 0.0f ); } } /* ===================== MSG_ReadDeltaUsercmd ===================== */ void MSG_ReadDeltaUsercmd( sizebuf_t *msg, usercmd_t *from, usercmd_t *to ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "usercmd_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); *to = *from; // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_ReadField( msg, pField, from, to, 0.0f ); } } /* ============================================================================ event_args_t communication ============================================================================ */ /* ===================== MSG_WriteDeltaEvent ===================== */ void MSG_WriteDeltaEvent( sizebuf_t *msg, event_args_t *from, event_args_t *to ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "event_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); // activate fields and call custom encode func Delta_CustomEncode( dt, from, to ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_WriteField( msg, pField, from, to, 0.0f ); } } /* ===================== MSG_ReadDeltaEvent ===================== */ void MSG_ReadDeltaEvent( sizebuf_t *msg, event_args_t *from, event_args_t *to ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "event_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); *to = *from; // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_ReadField( msg, pField, from, to, 0.0f ); } } /* ============================================================================= movevars_t communication ============================================================================= */ bool MSG_WriteDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *to ) { delta_t *pField; delta_info_t *dt; int i, startBit; int numChanges = 0; dt = Delta_FindStruct( "movevars_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); startBit = msg->iCurBit; // activate fields and call custom encode func Delta_CustomEncode( dt, from, to ); BF_WriteByte( msg, svc_deltamovevars ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { if( Delta_WriteField( msg, pField, from, to, 0.0f )) numChanges++; } // if we have no changes - kill the message if( !numChanges ) { BF_SeekToBit( msg, startBit ); return false; } return true; } void MSG_ReadDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *to ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "movevars_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); *to = *from; // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_ReadField( msg, pField, from, to, 0.0f ); } } /* ============================================================================= clientdata_t communication ============================================================================= */ /* ================== MSG_WriteClientData Writes current client data only for local client Other clients can grab the client state from entity_state_t ================== */ void MSG_WriteClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, float timebase ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "clientdata_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); // activate fields and call custom encode func Delta_CustomEncode( dt, from, to ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_WriteField( msg, pField, from, to, timebase ); } } /* ================== MSG_ReadClientData Read the clientdata ================== */ void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, float timebase ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "clientdata_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); *to = *from; // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_ReadField( msg, pField, from, to, timebase ); } } /* ============================================================================= weapon_data_t communication ============================================================================= */ /* ================== MSG_WriteWeaponData Writes current client data only for local client Other clients can grab the client state from entity_state_t ================== */ void MSG_WriteWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, float timebase, int index ) { delta_t *pField; delta_info_t *dt; int i, startBit; int numChanges = 0; dt = Delta_FindStruct( "weapon_data_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); // activate fields and call custom encode func Delta_CustomEncode( dt, from, to ); startBit = msg->iCurBit; BF_WriteOneBit( msg, 1 ); BF_WriteUBitLong( msg, index, MAX_WEAPON_BITS ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { if( Delta_WriteField( msg, pField, from, to, timebase )) numChanges++; } // if we have no changes - kill the message if( !numChanges ) BF_SeekToBit( msg, startBit ); } /* ================== MSG_ReadWeaponData Read the clientdata ================== */ void MSG_ReadWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, float timebase ) { delta_t *pField; delta_info_t *dt; int i; dt = Delta_FindStruct( "weapon_data_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); *to = *from; // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_ReadField( msg, pField, from, to, timebase ); } } /* ============================================================================= entity_state_t communication ============================================================================= */ /* ================== 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( entity_state_t *from, entity_state_t *to, sizebuf_t *msg, bool force, float timebase ) { delta_info_t *dt; delta_t *pField; int i, startBit; int numChanges = 0; if( to == NULL ) { int fRemoveType; if( from == NULL ) return; // a NULL to is a delta remove message BF_WriteWord( msg, from->number ); // fRemoveType: // 0 - keep alive, has delta-update // 1 - remove from delta message (but keep states) // 2 - completely remove from server if( force ) fRemoveType = 2; else fRemoveType = 1; BF_WriteUBitLong( msg, fRemoveType, 2 ); return; } startBit = msg->iCurBit; if( to->number < 0 || to->number >= GI->max_edicts ) Host_Error( "MSG_WriteDeltaEntity: Bad entity number: %i\n", to->number ); BF_WriteWord( msg, to->number ); BF_WriteUBitLong( msg, 0, 2 ); // alive if( to->entityType != from->entityType ) { BF_WriteOneBit( msg, 1 ); BF_WriteUBitLong( msg, to->entityType, 4 ); } else BF_WriteOneBit( msg, 0 ); switch( to->entityType ) { case ET_PLAYER: dt = Delta_FindStruct( "entity_state_player_t" ); break; case ET_BEAM: dt = Delta_FindStruct( "custom_entity_state_t" ); break; default: dt = Delta_FindStruct( "entity_state_t" ); break; } ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); // activate fields and call custom encode func Delta_CustomEncode( dt, from, to ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { if( Delta_WriteField( msg, pField, from, to, timebase )) numChanges++; } // if we have no changes - kill the message if( !numChanges && !force ) BF_SeekToBit( msg, startBit ); } /* ================== 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, entity_state_t->number will be set to MAX_EDICTS Can go from either a baseline or a previous packet_entity ================== */ bool MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state_t *to, int number, float timebase ) { delta_info_t *dt; delta_t *pField; int i, fRemoveType; if( number < 0 || number >= GI->max_edicts ) Host_Error( "MSG_ReadDeltaEntity: bad delta entity number: %i\n", number ); *to = *from; to->number = number; fRemoveType = BF_ReadUBitLong( msg, 2 ); if( fRemoveType ) { // check for a remove Mem_Set( to, 0, sizeof( *to )); if( fRemoveType & 1 ) { // removed from delta-message return false; } if( fRemoveType & 2 ) { // entity was removed from server to->number = -1; return false; } Host_Error( "MSG_ReadDeltaEntity: unknown update type %i\n", fRemoveType ); } if( BF_ReadOneBit( msg )) to->entityType = BF_ReadUBitLong( msg, 4 ); switch( to->entityType ) { case ET_PLAYER: dt = Delta_FindStruct( "entity_state_player_t" ); break; case ET_BEAM: dt = Delta_FindStruct( "custom_entity_state_t" ); break; default: dt = Delta_FindStruct( "entity_state_t" ); break; } ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { Delta_ReadField( msg, pField, from, to, timebase ); } // message parsed return true; } void Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc ) { delta_info_t *dt; dt = Delta_FindStructByEncoder( name ); if( !dt || !dt->bInitialized ) { MsgDev( D_ERROR, "Delta_AddEncoder: couldn't find delta with specified custom encode %s\n", name ); return; } if( dt->customEncode == CUSTOM_NONE ) { MsgDev( D_ERROR, "Delta_AddEncoder: %s not supposed for custom encoding\n", dt->pName ); return; } // register new encode func dt->userCallback = encodeFunc; } int Delta_FindField( delta_t *pFields, const char *fieldname ) { delta_info_t *dt; delta_t *pField; int i; dt = Delta_FindStructByDelta( pFields ); if( dt == NULL || !fieldname || !fieldname[0] ) return -1; for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) { if( !com.strcmp( pField->name, fieldname )) return i; } return -1; } void Delta_SetField( delta_t *pFields, const char *fieldname ) { delta_info_t *dt; delta_t *pField; int i; dt = Delta_FindStructByDelta( pFields ); if( dt == NULL || !fieldname || !fieldname[0] ) return; for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) { if( !com.strcmp( pField->name, fieldname )) { pField->bInactive = false; return; } } } void Delta_UnsetField( delta_t *pFields, const char *fieldname ) { delta_info_t *dt; delta_t *pField; int i; dt = Delta_FindStructByDelta( pFields ); if( dt == NULL || !fieldname || !fieldname[0] ) return; for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) { if( !com.strcmp( pField->name, fieldname )) { pField->bInactive = true; return; } } } void Delta_SetFieldByIndex( delta_t *pFields, int fieldNumber ) { delta_info_t *dt; dt = Delta_FindStructByDelta( pFields ); if( dt == NULL || fieldNumber < 0 || fieldNumber >= dt->numFields ) return; dt->pFields[fieldNumber].bInactive = false; } void Delta_UnsetFieldByIndex( delta_t *pFields, int fieldNumber ) { delta_info_t *dt; dt = Delta_FindStructByDelta( pFields ); if( dt == NULL || fieldNumber < 0 || fieldNumber >= dt->numFields ) return; dt->pFields[fieldNumber].bInactive = true; }