//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Helper classes and functions for the save/restore system.
//
// $NoKeywords: $
//=============================================================================//
# include "cbase.h"
# include <limits.h>
# include "isaverestore.h"
# include "saverestore.h"
# include <stdarg.h>
# include "shake.h"
# include "decals.h"
# include "gamerules.h"
# include "bspfile.h"
# include "mathlib/mathlib.h"
# include "engine/IEngineSound.h"
# include "saverestoretypes.h"
# include "saverestore_utlvector.h"
# include "model_types.h"
# include "igamesystem.h"
# include "interval.h"
# include "vphysics/object_hash.h"
# include "datacache/imdlcache.h"
# include "tier0/vprof.h"
# if !defined( CLIENT_DLL )
# include "globalstate.h"
# include "entitylist.h"
# else
# include "gamestringpool.h"
# endif
// HACKHACK: Builds a global list of entities that were restored from all levels
# if !defined( CLIENT_DLL )
void AddRestoredEntity ( CBaseEntity * pEntity ) ;
# else
void AddRestoredEntity ( C_BaseEntity * pEntity ) ;
# endif
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
# define MAX_ENTITYARRAY 1024
# define ZERO_TIME ((FLT_MAX*-0.5))
// A bit arbitrary, but unlikely to collide with any saved games...
# define TICK_NEVER_THINK_ENCODE ( INT_MAX - 3 )
ASSERT_INVARIANT ( sizeof ( EHandlePlaceholder_t ) = = sizeof ( EHANDLE ) ) ;
//-----------------------------------------------------------------------------
static int gSizes [ FIELD_TYPECOUNT ] =
{
FIELD_SIZE ( FIELD_VOID ) ,
FIELD_SIZE ( FIELD_FLOAT ) ,
FIELD_SIZE ( FIELD_STRING ) ,
FIELD_SIZE ( FIELD_VECTOR ) ,
FIELD_SIZE ( FIELD_QUATERNION ) ,
FIELD_SIZE ( FIELD_INTEGER ) ,
FIELD_SIZE ( FIELD_BOOLEAN ) ,
FIELD_SIZE ( FIELD_SHORT ) ,
FIELD_SIZE ( FIELD_CHARACTER ) ,
FIELD_SIZE ( FIELD_COLOR32 ) ,
FIELD_SIZE ( FIELD_EMBEDDED ) ,
FIELD_SIZE ( FIELD_CUSTOM ) ,
FIELD_SIZE ( FIELD_CLASSPTR ) ,
FIELD_SIZE ( FIELD_EHANDLE ) ,
FIELD_SIZE ( FIELD_EDICT ) ,
FIELD_SIZE ( FIELD_POSITION_VECTOR ) ,
FIELD_SIZE ( FIELD_TIME ) ,
FIELD_SIZE ( FIELD_TICK ) ,
FIELD_SIZE ( FIELD_MODELNAME ) ,
FIELD_SIZE ( FIELD_SOUNDNAME ) ,
FIELD_SIZE ( FIELD_INPUT ) ,
FIELD_SIZE ( FIELD_FUNCTION ) ,
FIELD_SIZE ( FIELD_VMATRIX ) ,
FIELD_SIZE ( FIELD_VMATRIX_WORLDSPACE ) ,
FIELD_SIZE ( FIELD_MATRIX3X4_WORLDSPACE ) ,
FIELD_SIZE ( FIELD_INTERVAL ) ,
FIELD_SIZE ( FIELD_MODELINDEX ) ,
FIELD_SIZE ( FIELD_MATERIALINDEX ) ,
FIELD_SIZE ( FIELD_VECTOR2D ) ,
} ;
// helpers to offset worldspace matrices
static void VMatrixOffset ( VMatrix & dest , const VMatrix & matrixIn , const Vector & offset )
{
dest = matrixIn ;
dest . PostTranslate ( offset ) ;
}
static void Matrix3x4Offset ( matrix3x4_t & dest , const matrix3x4_t & matrixIn , const Vector & offset )
{
MatrixCopy ( matrixIn , dest ) ;
Vector out ;
MatrixGetColumn ( matrixIn , 3 , out ) ;
out + = offset ;
MatrixSetColumn ( out , 3 , dest ) ;
}
// This does the necessary casting / extract to grab a pointer to a member function as a void *
// UNDONE: Cast to BASEPTR or something else here?
//#define EXTRACT_INPUTFUNC_FUNCTIONPTR(x) (*(inputfunc_t **)(&(x)))
//-----------------------------------------------------------------------------
// Purpose: Search this datamap for the name of this member function
// This is used to save/restore function pointers (convert pointer to text)
// Input : *function - pointer to member function
// Output : const char * - function name
//-----------------------------------------------------------------------------
const char * UTIL_FunctionToName ( datamap_t * pMap , inputfunc_t function )
{
while ( pMap )
{
for ( int i = 0 ; i < pMap - > dataNumFields ; i + + )
{
if ( pMap - > dataDesc [ i ] . flags & FTYPEDESC_FUNCTIONTABLE )
{
# ifdef WIN32
Assert ( sizeof ( pMap - > dataDesc [ i ] . inputFunc ) = = sizeof ( void * ) ) ;
# elif defined(POSIX)
Assert ( sizeof ( pMap - > dataDesc [ i ] . inputFunc ) = = 8 ) ;
# else
# error
# endif
inputfunc_t pTest = pMap - > dataDesc [ i ] . inputFunc ;
if ( pTest = = function )
return pMap - > dataDesc [ i ] . fieldName ;
}
}
pMap = pMap - > baseMap ;
}
return NULL ;
}
//-----------------------------------------------------------------------------
// Purpose: Search the datamap for a function named pName
// This is used to save/restore function pointers (convert text back to pointer)
// Input : *pName - name of the member function
//-----------------------------------------------------------------------------
inputfunc_t UTIL_FunctionFromName ( datamap_t * pMap , const char * pName )
{
while ( pMap )
{
for ( int i = 0 ; i < pMap - > dataNumFields ; i + + )
{
# ifdef WIN32
Assert ( sizeof ( pMap - > dataDesc [ i ] . inputFunc ) = = sizeof ( void * ) ) ;
# elif defined(POSIX)
Assert ( sizeof ( pMap - > dataDesc [ i ] . inputFunc ) = = 8 ) ;
# else
# error
# endif
if ( pMap - > dataDesc [ i ] . flags & FTYPEDESC_FUNCTIONTABLE )
{
if ( FStrEq ( pName , pMap - > dataDesc [ i ] . fieldName ) )
{
return pMap - > dataDesc [ i ] . inputFunc ;
}
}
}
pMap = pMap - > baseMap ;
}
Msg ( " Failed to find function %s \n " , pName ) ;
return NULL ;
}
//-----------------------------------------------------------------------------
//
// CSave
//
//-----------------------------------------------------------------------------
CSave : : CSave ( CSaveRestoreData * pdata )
: m_pData ( pdata ) ,
m_pGameInfo ( pdata ) ,
m_bAsync ( pdata - > bAsync )
{
m_BlockStartStack . EnsureCapacity ( 32 ) ;
// Logging.
m_hLogFile = NULL ;
}
//-------------------------------------
inline int CSave : : DataEmpty ( const char * pdata , int size )
{
if ( size ! = 4 )
{
const char * pLimit = pdata + size ;
while ( pdata < pLimit )
{
if ( * pdata + + )
return 0 ;
}
return 1 ;
}
return ( * ( ( int * ) pdata ) = = 0 ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Start logging save data.
//-----------------------------------------------------------------------------
void CSave : : StartLogging ( const char * pszLogName )
{
m_hLogFile = filesystem - > Open ( pszLogName , " w " ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Stop logging save data.
//-----------------------------------------------------------------------------
void CSave : : EndLogging ( void )
{
if ( m_hLogFile )
{
filesystem - > Close ( m_hLogFile ) ;
}
m_hLogFile = NULL ;
}
//-----------------------------------------------------------------------------
// Purpose: Check to see if we are logging data.
//-----------------------------------------------------------------------------
bool CSave : : IsLogging ( void )
{
return ( m_hLogFile ! = NULL ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Log data.
//-----------------------------------------------------------------------------
void CSave : : Log ( const char * pName , fieldtype_t fieldType , void * value , int count )
{
// Check to see if we are logging.
if ( ! IsLogging ( ) )
return ;
static char szBuf [ 1024 ] ;
static char szTempBuf [ 256 ] ;
// Save the name.
Q_snprintf ( szBuf , sizeof ( szBuf ) , " %s " , pName ) ;
for ( int iCount = 0 ; iCount < count ; + + iCount )
{
switch ( fieldType )
{
case FIELD_SHORT :
{
short * pValue = ( short * ) ( value ) ;
short nValue = pValue [ iCount ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " %d " , nValue ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
break ;
}
case FIELD_FLOAT :
{
float * pValue = ( float * ) ( value ) ;
float flValue = pValue [ iCount ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " %f " , flValue ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
break ;
}
case FIELD_BOOLEAN :
{
bool * pValue = ( bool * ) ( value ) ;
bool bValue = pValue [ iCount ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " %d " , ( int ) ( bValue ) ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
break ;
}
case FIELD_INTEGER :
{
int * pValue = ( int * ) ( value ) ;
int nValue = pValue [ iCount ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " %d " , nValue ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
break ;
}
case FIELD_STRING :
{
string_t * pValue = ( string_t * ) ( value ) ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " %s " , ( char * ) STRING ( * pValue ) ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
break ;
}
case FIELD_VECTOR :
{
Vector * pValue = ( Vector * ) ( value ) ;
Vector vecValue = pValue [ iCount ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " (%f %f %f) " , vecValue . x , vecValue . y , vecValue . z ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
break ;
}
case FIELD_QUATERNION :
{
Quaternion * pValue = ( Quaternion * ) ( value ) ;
Quaternion q = pValue [ iCount ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " (%f %f %f %f) " , q [ 0 ] , q [ 1 ] , q [ 2 ] , q [ 3 ] ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
break ;
}
case FIELD_CHARACTER :
{
char * pValue = ( char * ) ( value ) ;
char chValue = pValue [ iCount ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " %c " , chValue ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
}
case FIELD_COLOR32 :
{
byte * pValue = ( byte * ) ( value ) ;
byte * pColor = & pValue [ iCount * 4 ] ;
Q_snprintf ( szTempBuf , sizeof ( szTempBuf ) , " (%d %d %d %d) " , ( int ) pColor [ 0 ] , ( int ) pColor [ 1 ] , ( int ) pColor [ 2 ] , ( int ) pColor [ 3 ] ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
}
case FIELD_EMBEDDED :
case FIELD_CUSTOM :
default :
{
break ;
}
}
// Add space data.
if ( ( iCount + 1 ) ! = count )
{
Q_strncpy ( szTempBuf , " " , sizeof ( szTempBuf ) ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
}
else
{
Q_strncpy ( szTempBuf , " \n " , sizeof ( szTempBuf ) ) ;
Q_strncat ( szBuf , szTempBuf , sizeof ( szTempBuf ) , COPY_ALL_CHARACTERS ) ;
}
}
int nLength = strlen ( szBuf ) + 1 ;
filesystem - > Write ( szBuf , nLength , m_hLogFile ) ;
}
//-------------------------------------
bool CSave : : IsAsync ( )
{
return m_bAsync ;
}
//-------------------------------------
int CSave : : GetWritePos ( ) const
{
return m_pData - > GetCurPos ( ) ;
}
//-------------------------------------
void CSave : : SetWritePos ( int pos )
{
m_pData - > Seek ( pos ) ;
}
//-------------------------------------
void CSave : : WriteShort ( const short * value , int count )
{
BufferData ( ( const char * ) value , sizeof ( short ) * count ) ;
}
//-------------------------------------
void CSave : : WriteInt ( const int * value , int count )
{
BufferData ( ( const char * ) value , sizeof ( int ) * count ) ;
}
//-------------------------------------
void CSave : : WriteBool ( const bool * value , int count )
{
COMPILE_TIME_ASSERT ( sizeof ( bool ) = = sizeof ( char ) ) ;
BufferData ( ( const char * ) value , sizeof ( bool ) * count ) ;
}
//-------------------------------------
void CSave : : WriteFloat ( const float * value , int count )
{
BufferData ( ( const char * ) value , sizeof ( float ) * count ) ;
}
//-------------------------------------
void CSave : : WriteData ( const char * pdata , int size )
{
BufferData ( pdata , size ) ;
}
//-------------------------------------
void CSave : : WriteString ( const char * pstring )
{
BufferData ( pstring , strlen ( pstring ) + 1 ) ;
}
//-------------------------------------
void CSave : : WriteString ( const string_t * stringId , int count )
{
for ( int i = 0 ; i < count ; i + + )
{
const char * pString = STRING ( stringId [ i ] ) ;
BufferData ( pString , strlen ( pString ) + 1 ) ;
}
}
//-------------------------------------
void CSave : : WriteVector ( const Vector & value )
{
BufferData ( ( const char * ) & value , sizeof ( Vector ) ) ;
}
//-------------------------------------
void CSave : : WriteVector ( const Vector * value , int count )
{
BufferData ( ( const char * ) value , sizeof ( Vector ) * count ) ;
}
void CSave : : WriteQuaternion ( const Quaternion & value )
{
BufferData ( ( const char * ) & value , sizeof ( Quaternion ) ) ;
}
//-------------------------------------
void CSave : : WriteQuaternion ( const Quaternion * value , int count )
{
BufferData ( ( const char * ) value , sizeof ( Quaternion ) * count ) ;
}
//-------------------------------------
void CSave : : WriteData ( const char * pname , int size , const char * pdata )
{
BufferField ( pname , size , pdata ) ;
}
//-------------------------------------
void CSave : : WriteShort ( const char * pname , const short * data , int count )
{
BufferField ( pname , sizeof ( short ) * count , ( const char * ) data ) ;
}
//-------------------------------------
void CSave : : WriteInt ( const char * pname , const int * data , int count )
{
BufferField ( pname , sizeof ( int ) * count , ( const char * ) data ) ;
}
//-------------------------------------
void CSave : : WriteBool ( const char * pname , const bool * data , int count )
{
COMPILE_TIME_ASSERT ( sizeof ( bool ) = = sizeof ( char ) ) ;
BufferField ( pname , sizeof ( bool ) * count , ( const char * ) data ) ;
}
//-------------------------------------
void CSave : : WriteFloat ( const char * pname , const float * data , int count )
{
BufferField ( pname , sizeof ( float ) * count , ( const char * ) data ) ;
}
//-------------------------------------
void CSave : : WriteString ( const char * pname , const char * pdata )
{
BufferField ( pname , strlen ( pdata ) + 1 , pdata ) ;
}
//-------------------------------------
void CSave : : WriteString ( const char * pname , const string_t * stringId , int count )
{
int i , size ;
size = 0 ;
for ( i = 0 ; i < count ; i + + )
size + = strlen ( STRING ( stringId [ i ] ) ) + 1 ;
WriteHeader ( pname , size ) ;
WriteString ( stringId , count ) ;
}
//-------------------------------------
void CSave : : WriteVector ( const char * pname , const Vector & value )
{
WriteVector ( pname , & value , 1 ) ;
}
//-------------------------------------
void CSave : : WriteVector ( const char * pname , const Vector * value , int count )
{
WriteHeader ( pname , sizeof ( Vector ) * count ) ;
BufferData ( ( const char * ) value , sizeof ( Vector ) * count ) ;
}
void CSave : : WriteQuaternion ( const char * pname , const Quaternion & value )
{
WriteQuaternion ( pname , & value , 1 ) ;
}
//-------------------------------------
void CSave : : WriteQuaternion ( const char * pname , const Quaternion * value , int count )
{
WriteHeader ( pname , sizeof ( Quaternion ) * count ) ;
BufferData ( ( const char * ) value , sizeof ( Quaternion ) * count ) ;
}
//-------------------------------------
void CSave : : WriteVMatrix ( const VMatrix * value , int count )
{
BufferData ( ( const char * ) value , sizeof ( VMatrix ) * count ) ;
}
//-------------------------------------
void CSave : : WriteVMatrix ( const char * pname , const VMatrix * value , int count )
{
WriteHeader ( pname , sizeof ( VMatrix ) * count ) ;
BufferData ( ( const char * ) value , sizeof ( VMatrix ) * count ) ;
}
//-------------------------------------
void CSave : : WriteVMatrixWorldspace ( const VMatrix * value , int count )
{
for ( int i = 0 ; i < count ; i + + )
{
VMatrix tmp ;
VMatrixOffset ( tmp , value [ i ] , - m_pGameInfo - > GetLandmark ( ) ) ;
BufferData ( ( const char * ) & tmp , sizeof ( VMatrix ) ) ;
}
}
//-------------------------------------
void CSave : : WriteVMatrixWorldspace ( const char * pname , const VMatrix * value , int count )
{
WriteHeader ( pname , sizeof ( VMatrix ) * count ) ;
WriteVMatrixWorldspace ( value , count ) ;
}
void CSave : : WriteMatrix3x4Worldspace ( const matrix3x4_t * value , int count )
{
Vector offset = - m_pGameInfo - > GetLandmark ( ) ;
for ( int i = 0 ; i < count ; i + + )
{
matrix3x4_t tmp ;
Matrix3x4Offset ( tmp , value [ i ] , offset ) ;
BufferData ( ( const char * ) value , sizeof ( matrix3x4_t ) ) ;
}
}
//-------------------------------------
void CSave : : WriteMatrix3x4Worldspace ( const char * pname , const matrix3x4_t * value , int count )
{
WriteHeader ( pname , sizeof ( matrix3x4_t ) * count ) ;
WriteMatrix3x4Worldspace ( value , count ) ;
}
void CSave : : WriteInterval ( const char * pname , const interval_t * value , int count )
{
WriteHeader ( pname , sizeof ( interval_t ) * count ) ;
WriteInterval ( value , count ) ;
}
void CSave : : WriteInterval ( const interval_t * value , int count )
{
BufferData ( ( const char * ) value , count * sizeof ( interval_t ) ) ;
}
//-------------------------------------
bool CSave : : ShouldSaveField ( const void * pData , typedescription_t * pField )
{
if ( ! ( pField - > flags & FTYPEDESC_SAVE ) | | pField - > fieldType = = FIELD_VOID )
return false ;
switch ( pField - > fieldType )
{
case FIELD_EMBEDDED :
{
if ( pField - > flags & FTYPEDESC_PTR )
{
AssertMsg ( pField - > fieldSize = = 1 , " Arrays of embedded pointer types presently unsupported by save/restore " ) ;
if ( pField - > fieldSize ! = 1 )
return false ;
}
AssertMsg ( pField - > td ! = NULL , " Embedded type appears to have not had type description implemented " ) ;
if ( pField - > td = = NULL )
return false ;
if ( ( pField - > flags & FTYPEDESC_PTR ) & & ! * ( ( void * * ) pData ) )
return false ;
// @TODO: need real logic for handling embedded types with base classes
if ( pField - > td - > baseMap )
{
return true ;
}
int nFieldCount = pField - > fieldSize ;
char * pTestData = ( char * ) ( ( ! ( pField - > flags & FTYPEDESC_PTR ) ) ? pData : * ( ( void * * ) pData ) ) ;
while ( - - nFieldCount > = 0 )
{
typedescription_t * pTestField = pField - > td - > dataDesc ;
typedescription_t * pLimit = pField - > td - > dataDesc + pField - > td - > dataNumFields ;
for ( ; pTestField < pLimit ; + + pTestField )
{
if ( ShouldSaveField ( pTestData + pTestField - > fieldOffset [ TD_OFFSET_NORMAL ] , pTestField ) )
return true ;
}
pTestData + = pField - > fieldSizeInBytes ;
}
return false ;
}
case FIELD_CUSTOM :
{
// ask the data if it's empty
SaveRestoreFieldInfo_t fieldInfo =
{
const_cast < void * > ( pData ) ,
( ( char * ) pData ) - pField - > fieldOffset [ TD_OFFSET_NORMAL ] ,
pField
} ;
if ( pField - > pSaveRestoreOps - > IsEmpty ( fieldInfo ) )
return false ;
}
return true ;
case FIELD_EHANDLE :
{
if ( ( pField - > fieldSizeInBytes ! = pField - > fieldSize * gSizes [ pField - > fieldType ] ) )
{
Warning ( " WARNING! Field %s is using the wrong FIELD_ type! \n Fix this or you'll see a crash. \n " , pField - > fieldName ) ;
Assert ( 0 ) ;
}
int * pEHandle = ( int * ) pData ;
for ( int i = 0 ; i < pField - > fieldSize ; + + i , + + pEHandle )
{
if ( ( * pEHandle ) ! = 0xFFFFFFFF )
return true ;
}
}
return false ;
default :
{
if ( ( pField - > fieldSizeInBytes ! = pField - > fieldSize * gSizes [ pField - > fieldType ] ) )
{
Warning ( " WARNING! Field %s is using the wrong FIELD_ type! \n Fix this or you'll see a crash. \n " , pField - > fieldName ) ;
Assert ( 0 ) ;
}
// old byte-by-byte null check
if ( DataEmpty ( ( const char * ) pData , pField - > fieldSize * gSizes [ pField - > fieldType ] ) )
return false ;
}
return true ;
}
}
//-------------------------------------
// Purpose: Writes all the fields that are client neutral. In the event of
// a librarization of save/restore, these would reside in the library
//
bool CSave : : WriteBasicField ( const char * pname , void * pData , datamap_t * pRootMap , typedescription_t * pField )
{
switch ( pField - > fieldType )
{
case FIELD_FLOAT :
WriteFloat ( pField - > fieldName , ( float * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_STRING :
WriteString ( pField - > fieldName , ( string_t * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_VECTOR :
WriteVector ( pField - > fieldName , ( Vector * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_QUATERNION :
WriteQuaternion ( pField - > fieldName , ( Quaternion * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_INTEGER :
WriteInt ( pField - > fieldName , ( int * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_BOOLEAN :
WriteBool ( pField - > fieldName , ( bool * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_SHORT :
WriteData ( pField - > fieldName , 2 * pField - > fieldSize , ( ( char * ) pData ) ) ;
break ;
case FIELD_CHARACTER :
WriteData ( pField - > fieldName , pField - > fieldSize , ( ( char * ) pData ) ) ;
break ;
case FIELD_COLOR32 :
WriteData ( pField - > fieldName , 4 * pField - > fieldSize , ( char * ) pData ) ;
break ;
case FIELD_EMBEDDED :
{
AssertMsg ( ( ( pField - > flags & FTYPEDESC_PTR ) = = 0 ) | | ( pField - > fieldSize = = 1 ) , " Arrays of embedded pointer types presently unsupported by save/restore " ) ;
Assert ( ! ( pField - > flags & FTYPEDESC_PTR ) | | * ( ( void * * ) pData ) ) ;
int nFieldCount = pField - > fieldSize ;
char * pFieldData = ( char * ) ( ( ! ( pField - > flags & FTYPEDESC_PTR ) ) ? pData : * ( ( void * * ) pData ) ) ;
StartBlock ( pField - > fieldName ) ;
while ( - - nFieldCount > = 0 )
{
WriteAll ( pFieldData , pField - > td ) ;
pFieldData + = pField - > fieldSizeInBytes ;
}
EndBlock ( ) ;
break ;
}
case FIELD_CUSTOM :
{
// Note it is up to the custom type implementor to handle arrays
StartBlock ( pField - > fieldName ) ;
SaveRestoreFieldInfo_t fieldInfo =
{
pData ,
( ( char * ) pData ) - pField - > fieldOffset [ TD_OFFSET_NORMAL ] ,
pField
} ;
pField - > pSaveRestoreOps - > Save ( fieldInfo , this ) ;
EndBlock ( ) ;
break ;
}
default :
Warning ( " Bad field type \n " ) ;
Assert ( 0 ) ;
return false ;
}
return true ;
}
//-------------------------------------
bool CSave : : WriteField ( const char * pname , void * pData , datamap_t * pRootMap , typedescription_t * pField )
{
# ifdef _DEBUG
Log ( pname , ( fieldtype_t ) pField - > fieldType , pData , pField - > fieldSize ) ;
# endif
if ( pField - > fieldType < = FIELD_CUSTOM )
{
return WriteBasicField ( pname , pData , pRootMap , pField ) ;
}
return WriteGameField ( pname , pData , pRootMap , pField ) ;
}
//-------------------------------------
int CSave : : WriteFields ( const char * pname , const void * pBaseData , datamap_t * pRootMap , typedescription_t * pFields , int fieldCount )
{
typedescription_t * pTest ;
int iHeaderPos = m_pData - > GetCurPos ( ) ;
int count = - 1 ;
WriteInt ( pname , & count , 1 ) ;
count = 0 ;
# ifdef _X360
__dcbt ( 0 , pBaseData ) ;
__dcbt ( 128 , pBaseData ) ;
__dcbt ( 256 , pBaseData ) ;
__dcbt ( 512 , pBaseData ) ;
void * pDest = m_pData - > AccessCurPos ( ) ;
__dcbt ( 0 , pDest ) ;
__dcbt ( 128 , pDest ) ;
__dcbt ( 256 , pDest ) ;
__dcbt ( 512 , pDest ) ;
# endif
for ( int i = 0 ; i < fieldCount ; i + + )
{
pTest = & pFields [ i ] ;
void * pOutputData = ( ( char * ) pBaseData + pTest - > fieldOffset [ TD_OFFSET_NORMAL ] ) ;
if ( ! ShouldSaveField ( pOutputData , pTest ) )
continue ;
if ( ! WriteField ( pname , pOutputData , pRootMap , pTest ) )
break ;
count + + ;
}
int iCurPos = m_pData - > GetCurPos ( ) ;
int iRewind = iCurPos - iHeaderPos ;
m_pData - > Rewind ( iRewind ) ;
WriteInt ( pname , & count , 1 ) ;
iCurPos = m_pData - > GetCurPos ( ) ;
m_pData - > MoveCurPos ( iRewind - ( iCurPos - iHeaderPos ) ) ;
return 1 ;
}
//-------------------------------------
// Purpose: Recursively saves all the classes in an object, in reverse order (top down)
// Output : int 0 on failure, 1 on success
int CSave : : DoWriteAll ( const void * pLeafObject , datamap_t * pLeafMap , datamap_t * pCurMap )
{
// save base classes first
if ( pCurMap - > baseMap )
{
int status = DoWriteAll ( pLeafObject , pLeafMap , pCurMap - > baseMap ) ;
if ( ! status )
return status ;
}
return WriteFields ( pCurMap - > dataClassName , pLeafObject , pLeafMap , pCurMap - > dataDesc , pCurMap - > dataNumFields ) ;
}
//-------------------------------------
void CSave : : StartBlock ( const char * pszBlockName )
{
WriteHeader ( pszBlockName , 0 ) ; // placeholder
m_BlockStartStack . AddToTail ( GetWritePos ( ) ) ;
}
//-------------------------------------
void CSave : : StartBlock ( )
{
StartBlock ( " " ) ;
}
//-------------------------------------
void CSave : : EndBlock ( )
{
int endPos = GetWritePos ( ) ;
int startPos = m_BlockStartStack [ m_BlockStartStack . Count ( ) - 1 ] ;
short sizeBlock = endPos - startPos ;
m_BlockStartStack . Remove ( m_BlockStartStack . Count ( ) - 1 ) ;
// Move to the the location where the size of the block was written & rewrite the size
SetWritePos ( startPos - sizeof ( SaveRestoreRecordHeader_t ) ) ;
BufferData ( ( const char * ) & sizeBlock , sizeof ( short ) ) ;
SetWritePos ( endPos ) ;
}
//-------------------------------------
void CSave : : BufferString ( char * pdata , int len )
{
char c = 0 ;
BufferData ( pdata , len ) ; // Write the string
BufferData ( & c , 1 ) ; // Write a null terminator
}
//-------------------------------------
void CSave : : BufferField ( const char * pname , int size , const char * pdata )
{
WriteHeader ( pname , size ) ;
BufferData ( pdata , size ) ;
}
//-------------------------------------
void CSave : : WriteHeader ( const char * pname , int size )
{
short shortSize = size ;
short hashvalue = m_pData - > FindCreateSymbol ( pname ) ;
if ( size > SHRT_MAX | | size < 0 )
{
Warning ( " CSave::WriteHeader() size parameter exceeds 'short'! \n " ) ;
Assert ( 0 ) ;
}
BufferData ( ( const char * ) & shortSize , sizeof ( short ) ) ;
BufferData ( ( const char * ) & hashvalue , sizeof ( short ) ) ;
}
//-------------------------------------
void CSave : : BufferData ( const char * pdata , int size )
{
if ( ! m_pData )
return ;
if ( ! m_pData - > Write ( pdata , size ) )
{
Warning ( " Save/Restore overflow! \n " ) ;
Assert ( 0 ) ;
}
}
//---------------------------------------------------------
//
// Game centric save methods.
//
int CSave : : EntityIndex ( const edict_t * pentLookup )
{
# if !defined( CLIENT_DLL )
if ( pentLookup = = NULL )
return - 1 ;
return EntityIndex ( CBaseEntity : : Instance ( pentLookup ) ) ;
# else
Assert ( ! " CSave::EntityIndex( edict_t * ) not valid on client! " ) ;
return - 1 ;
# endif
}
//-------------------------------------
int CSave : : EntityIndex ( const CBaseEntity * pEntity )
{
return m_pGameInfo - > GetEntityIndex ( pEntity ) ;
}
//-------------------------------------
int CSave : : EntityFlagsSet ( int entityIndex , int flags )
{
if ( ! m_pGameInfo | | entityIndex < 0 )
return 0 ;
if ( entityIndex > m_pGameInfo - > NumEntities ( ) )
return 0 ;
m_pGameInfo - > GetEntityInfo ( entityIndex ) - > flags | = flags ;
return m_pGameInfo - > GetEntityInfo ( entityIndex ) - > flags ;
}
//-------------------------------------
void CSave : : WriteTime ( const char * pname , const float * data , int count )
{
int i ;
float tmp ;
WriteHeader ( pname , sizeof ( float ) * count ) ;
for ( i = 0 ; i < count ; i + + )
{
// Always encode time as a delta from the current time so it can be re-based if loaded in a new level
// Times of 0 are never written to the file, so they will be restored as 0, not a relative time
Assert ( data [ i ] ! = ZERO_TIME ) ;
if ( data [ i ] = = 0.0 )
{
tmp = ZERO_TIME ;
}
else if ( data [ i ] = = INVALID_TIME | | data [ i ] = = FLT_MAX )
{
tmp = data [ i ] ;
}
else
{
tmp = data [ i ] - m_pGameInfo - > GetBaseTime ( ) ;
if ( fabsf ( tmp ) < 0.001 ) // never allow a time to become zero due to rebasing
tmp = 0.001 ;
}
WriteData ( ( const char * ) & tmp , sizeof ( float ) ) ;
}
}
//-------------------------------------
void CSave : : WriteTime ( const float * data , int count )
{
int i ;
float tmp ;
for ( i = 0 ; i < count ; i + + )
{
// Always encode time as a delta from the current time so it can be re-based if loaded in a new level
// Times of 0 are never written to the file, so they will be restored as 0, not a relative time
if ( data [ i ] = = 0.0 )
{
tmp = ZERO_TIME ;
}
else if ( data [ i ] = = INVALID_TIME | | data [ i ] = = FLT_MAX )
{
tmp = data [ i ] ;
}
else
{
tmp = data [ i ] - m_pGameInfo - > GetBaseTime ( ) ;
if ( fabsf ( tmp ) < 0.001 ) // never allow a time to become zero due to rebasing
tmp = 0.001 ;
}
WriteData ( ( const char * ) & tmp , sizeof ( float ) ) ;
}
}
void CSave : : WriteTick ( const char * pname , const int * data , int count )
{
WriteHeader ( pname , sizeof ( int ) * count ) ;
WriteTick ( data , count ) ;
}
//-------------------------------------
void CSave : : WriteTick ( const int * data , int count )
{
int i ;
int tmp ;
int baseTick = TIME_TO_TICKS ( m_pGameInfo - > GetBaseTime ( ) ) ;
for ( i = 0 ; i < count ; i + + )
{
// Always encode time as a delta from the current time so it can be re-based if loaded in a new level
// Times of 0 are never written to the file, so they will be restored as 0, not a relative time
tmp = data [ i ] ;
if ( data [ i ] = = TICK_NEVER_THINK )
{
tmp = TICK_NEVER_THINK_ENCODE ;
}
else
{
// Rebase it...
tmp - = baseTick ;
}
WriteData ( ( const char * ) & tmp , sizeof ( int ) ) ;
}
}
//-------------------------------------
void CSave : : WritePositionVector ( const char * pname , const Vector & value )
{
Vector tmp = value ;
if ( tmp ! = vec3_invalid )
tmp - = m_pGameInfo - > GetLandmark ( ) ;
WriteVector ( pname , tmp ) ;
}
//-------------------------------------
void CSave : : WritePositionVector ( const Vector & value )
{
Vector tmp = value ;
if ( tmp ! = vec3_invalid )
tmp - = m_pGameInfo - > GetLandmark ( ) ;
WriteVector ( tmp ) ;
}
//-------------------------------------
void CSave : : WritePositionVector ( const char * pname , const Vector * value , int count )
{
WriteHeader ( pname , sizeof ( Vector ) * count ) ;
WritePositionVector ( value , count ) ;
}
//-------------------------------------
void CSave : : WritePositionVector ( const Vector * value , int count )
{
for ( int i = 0 ; i < count ; i + + )
{
Vector tmp = value [ i ] ;
if ( tmp ! = vec3_invalid )
tmp - = m_pGameInfo - > GetLandmark ( ) ;
WriteData ( ( const char * ) & tmp . x , sizeof ( Vector ) ) ;
}
}
//-------------------------------------
void CSave : : WriteFunction ( datamap_t * pRootMap , const char * pname , inputfunc_t * * data , int count )
{
AssertMsg ( count = = 1 , " Arrays of functions not presently supported " ) ;
const char * functionName = UTIL_FunctionToName ( pRootMap , * ( inputfunc_t * ) data ) ;
if ( ! functionName )
{
Warning ( " Invalid function pointer in entity! \n " ) ;
Assert ( 0 ) ;
functionName = " BADFUNCTIONPOINTER " ;
}
BufferField ( pname , strlen ( functionName ) + 1 , functionName ) ;
}
//-------------------------------------
void CSave : : WriteEntityPtr ( const char * pname , CBaseEntity * * ppEntity , int count )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
for ( int i = 0 ; i < count & & i < MAX_ENTITYARRAY ; i + + )
{
entityArray [ i ] = EntityIndex ( ppEntity [ i ] ) ;
}
WriteInt ( pname , entityArray , count ) ;
}
//-------------------------------------
void CSave : : WriteEntityPtr ( CBaseEntity * * ppEntity , int count )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
for ( int i = 0 ; i < count & & i < MAX_ENTITYARRAY ; i + + )
{
entityArray [ i ] = EntityIndex ( ppEntity [ i ] ) ;
}
WriteInt ( entityArray , count ) ;
}
//-------------------------------------
void CSave : : WriteEdictPtr ( const char * pname , edict_t * * ppEdict , int count )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
for ( int i = 0 ; i < count & & i < MAX_ENTITYARRAY ; i + + )
{
entityArray [ i ] = EntityIndex ( ppEdict [ i ] ) ;
}
WriteInt ( pname , entityArray , count ) ;
}
//-------------------------------------
void CSave : : WriteEdictPtr ( edict_t * * ppEdict , int count )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
for ( int i = 0 ; i < count & & i < MAX_ENTITYARRAY ; i + + )
{
entityArray [ i ] = EntityIndex ( ppEdict [ i ] ) ;
}
WriteInt ( entityArray , count ) ;
}
//-------------------------------------
void CSave : : WriteEHandle ( const char * pname , const EHANDLE * pEHandle , int count )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
for ( int i = 0 ; i < count & & i < MAX_ENTITYARRAY ; i + + )
{
entityArray [ i ] = EntityIndex ( ( CBaseEntity * ) ( const_cast < EHANDLE * > ( pEHandle ) [ i ] ) ) ;
}
WriteInt ( pname , entityArray , count ) ;
}
//-------------------------------------
void CSave : : WriteEHandle ( const EHANDLE * pEHandle , int count )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
for ( int i = 0 ; i < count & & i < MAX_ENTITYARRAY ; i + + )
{
entityArray [ i ] = EntityIndex ( ( CBaseEntity * ) ( const_cast < EHANDLE * > ( pEHandle ) [ i ] ) ) ;
}
WriteInt ( entityArray , count ) ;
}
//-------------------------------------
// Purpose: Writes all the fields that are not client neutral. In the event of
// a librarization of save/restore, these would not reside in the library
bool CSave : : WriteGameField ( const char * pname , void * pData , datamap_t * pRootMap , typedescription_t * pField )
{
switch ( pField - > fieldType )
{
case FIELD_CLASSPTR :
WriteEntityPtr ( pField - > fieldName , ( CBaseEntity * * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_EDICT :
WriteEdictPtr ( pField - > fieldName , ( edict_t * * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_EHANDLE :
WriteEHandle ( pField - > fieldName , ( EHANDLE * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_POSITION_VECTOR :
WritePositionVector ( pField - > fieldName , ( Vector * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_TIME :
WriteTime ( pField - > fieldName , ( float * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_TICK :
WriteTick ( pField - > fieldName , ( int * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_MODELINDEX :
{
int nModelIndex = * ( int * ) pData ;
string_t strModelName = NULL_STRING ;
const model_t * pModel = modelinfo - > GetModel ( nModelIndex ) ;
if ( pModel )
{
strModelName = AllocPooledString ( modelinfo - > GetModelName ( pModel ) ) ;
}
WriteString ( pField - > fieldName , ( string_t * ) & strModelName , pField - > fieldSize ) ;
}
break ;
case FIELD_MATERIALINDEX :
{
int nMateralIndex = * ( int * ) pData ;
string_t strMaterialName = NULL_STRING ;
const char * pMaterialName = GetMaterialNameFromIndex ( nMateralIndex ) ;
if ( pMaterialName )
{
strMaterialName = MAKE_STRING ( pMaterialName ) ;
}
WriteString ( pField - > fieldName , ( string_t * ) & strMaterialName , pField - > fieldSize ) ;
}
break ;
case FIELD_MODELNAME :
case FIELD_SOUNDNAME :
WriteString ( pField - > fieldName , ( string_t * ) pData , pField - > fieldSize ) ;
break ;
// For now, just write the address out, we're not going to change memory while doing this yet!
case FIELD_FUNCTION :
WriteFunction ( pRootMap , pField - > fieldName , ( inputfunc_t * * ) ( char * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_VMATRIX :
WriteVMatrix ( pField - > fieldName , ( VMatrix * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_VMATRIX_WORLDSPACE :
WriteVMatrixWorldspace ( pField - > fieldName , ( VMatrix * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_MATRIX3X4_WORLDSPACE :
WriteMatrix3x4Worldspace ( pField - > fieldName , ( const matrix3x4_t * ) pData , pField - > fieldSize ) ;
break ;
case FIELD_INTERVAL :
WriteInterval ( pField - > fieldName , ( interval_t * ) pData , pField - > fieldSize ) ;
break ;
default :
Warning ( " Bad field type \n " ) ;
Assert ( 0 ) ;
return false ;
}
return true ;
}
//-----------------------------------------------------------------------------
//
// CRestore
//
//-----------------------------------------------------------------------------
CRestore : : CRestore ( CSaveRestoreData * pdata )
: m_pData ( pdata ) ,
m_pGameInfo ( pdata ) ,
m_global ( 0 ) ,
m_precache ( true )
{
m_BlockEndStack . EnsureCapacity ( 32 ) ;
}
//-------------------------------------
int CRestore : : GetReadPos ( ) const
{
return m_pData - > GetCurPos ( ) ;
}
//-------------------------------------
void CRestore : : SetReadPos ( int pos )
{
m_pData - > Seek ( pos ) ;
}
//-------------------------------------
const char * CRestore : : StringFromHeaderSymbol ( int symbol )
{
const char * pszResult = m_pData - > StringFromSymbol ( symbol ) ;
return ( pszResult ) ? pszResult : " " ;
}
//-------------------------------------
// Purpose: Reads all the fields that are client neutral. In the event of
// a librarization of save/restore, these would reside in the library
void CRestore : : ReadBasicField ( const SaveRestoreRecordHeader_t & header , void * pDest , datamap_t * pRootMap , typedescription_t * pField )
{
switch ( pField - > fieldType )
{
case FIELD_FLOAT :
{
ReadFloat ( ( float * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_STRING :
{
ReadString ( ( string_t * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_VECTOR :
{
ReadVector ( ( Vector * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_QUATERNION :
{
ReadQuaternion ( ( Quaternion * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_INTEGER :
{
ReadInt ( ( int * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_BOOLEAN :
{
ReadBool ( ( bool * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_SHORT :
{
ReadShort ( ( short * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_CHARACTER :
{
ReadData ( ( char * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_COLOR32 :
{
COMPILE_TIME_ASSERT ( sizeof ( color32 ) = = sizeof ( int ) ) ;
ReadInt ( ( int * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_EMBEDDED :
{
AssertMsg ( ( ( pField - > flags & FTYPEDESC_PTR ) = = 0 ) | | ( pField - > fieldSize = = 1 ) , " Arrays of embedded pointer types presently unsupported by save/restore " ) ;
# ifdef DBGFLAG_ASSERT
int startPos = GetReadPos ( ) ;
# endif
if ( ! ( pField - > flags & FTYPEDESC_PTR ) | | * ( ( void * * ) pDest ) )
{
int nFieldCount = pField - > fieldSize ;
char * pFieldData = ( char * ) ( ( ! ( pField - > flags & FTYPEDESC_PTR ) ) ? pDest : * ( ( void * * ) pDest ) ) ;
while ( - - nFieldCount > = 0 )
{
// No corresponding "block" (see write) as it was used as the header of the field
ReadAll ( pFieldData , pField - > td ) ;
pFieldData + = pField - > fieldSizeInBytes ;
}
Assert ( GetReadPos ( ) - startPos = = header . size ) ;
}
else
{
SetReadPos ( GetReadPos ( ) + header . size ) ;
Warning ( " Attempted to restore FIELD_EMBEDDEDBYREF %s but there is no destination memory \n " , pField - > fieldName ) ;
}
break ;
}
case FIELD_CUSTOM :
{
// No corresponding "block" (see write) as it was used as the header of the field
int posNextField = GetReadPos ( ) + header . size ;
SaveRestoreFieldInfo_t fieldInfo =
{
pDest ,
( ( char * ) pDest ) - pField - > fieldOffset [ TD_OFFSET_NORMAL ] ,
pField
} ;
pField - > pSaveRestoreOps - > Restore ( fieldInfo , this ) ;
Assert ( posNextField > = GetReadPos ( ) ) ;
SetReadPos ( posNextField ) ;
break ;
}
default :
Warning ( " Bad field type \n " ) ;
Assert ( 0 ) ;
}
}
//-------------------------------------
void CRestore : : ReadField ( const SaveRestoreRecordHeader_t & header , void * pDest , datamap_t * pRootMap , typedescription_t * pField )
{
if ( pField - > fieldType < = FIELD_CUSTOM )
ReadBasicField ( header , pDest , pRootMap , pField ) ;
else
ReadGameField ( header , pDest , pRootMap , pField ) ;
}
//-------------------------------------
bool CRestore : : ShouldReadField ( typedescription_t * pField )
{
if ( ( pField - > flags & FTYPEDESC_SAVE ) = = 0 )
return false ;
if ( m_global & & ( pField - > flags & FTYPEDESC_GLOBAL ) )
return false ;
return true ;
}
//-------------------------------------
typedescription_t * CRestore : : FindField ( const char * pszFieldName , typedescription_t * pFields , int fieldCount , int * pCookie )
{
int & fieldNumber = * pCookie ;
if ( pszFieldName )
{
typedescription_t * pTest ;
for ( int i = 0 ; i < fieldCount ; i + + )
{
pTest = & pFields [ fieldNumber ] ;
+ + fieldNumber ;
if ( fieldNumber = = fieldCount )
fieldNumber = 0 ;
if ( stricmp ( pTest - > fieldName , pszFieldName ) = = 0 )
return pTest ;
}
}
fieldNumber = 0 ;
return NULL ;
}
//-------------------------------------
bool CRestore : : ShouldEmptyField ( typedescription_t * pField )
{
// don't clear out fields that don't get saved, or that are handled specially
if ( ! ( pField - > flags & FTYPEDESC_SAVE ) )
return false ;
// Don't clear global fields
if ( m_global & & ( pField - > flags & FTYPEDESC_GLOBAL ) )
return false ;
return true ;
}
//-------------------------------------
void CRestore : : EmptyFields ( void * pBaseData , typedescription_t * pFields , int fieldCount )
{
int i ;
for ( i = 0 ; i < fieldCount ; i + + )
{
typedescription_t * pField = & pFields [ i ] ;
if ( ! ShouldEmptyField ( pField ) )
continue ;
void * pFieldData = ( char * ) pBaseData + pField - > fieldOffset [ TD_OFFSET_NORMAL ] ;
switch ( pField - > fieldType )
{
case FIELD_CUSTOM :
{
SaveRestoreFieldInfo_t fieldInfo =
{
pFieldData ,
pBaseData ,
pField
} ;
pField - > pSaveRestoreOps - > MakeEmpty ( fieldInfo ) ;
}
break ;
case FIELD_EMBEDDED :
{
if ( ( pField - > flags & FTYPEDESC_PTR ) & & ! * ( ( void * * ) pFieldData ) )
break ;
int nFieldCount = pField - > fieldSize ;
char * pFieldMemory = ( char * ) ( ( ! ( pField - > flags & FTYPEDESC_PTR ) ) ? pFieldData : * ( ( void * * ) pFieldData ) ) ;
while ( - - nFieldCount > = 0 )
{
EmptyFields ( pFieldMemory , pField - > td - > dataDesc , pField - > td - > dataNumFields ) ;
pFieldMemory + = pField - > fieldSizeInBytes ;
}
}
break ;
default :
// NOTE: If you hit this assertion, you've got a bug where you're using
// the wrong field type for your field
if ( pField - > fieldSizeInBytes ! = pField - > fieldSize * gSizes [ pField - > fieldType ] )
{
Warning ( " WARNING! Field %s is using the wrong FIELD_ type! \n Fix this or you'll see a crash. \n " , pField - > fieldName ) ;
Assert ( 0 ) ;
}
memset ( pFieldData , ( pField - > fieldType ! = FIELD_EHANDLE ) ? 0 : 0xFF , pField - > fieldSize * gSizes [ pField - > fieldType ] ) ;
break ;
}
}
}
//-------------------------------------
void CRestore : : StartBlock ( SaveRestoreRecordHeader_t * pHeader )
{
ReadHeader ( pHeader ) ;
m_BlockEndStack . AddToTail ( GetReadPos ( ) + pHeader - > size ) ;
}
//-------------------------------------
void CRestore : : StartBlock ( char szBlockName [ ] )
{
SaveRestoreRecordHeader_t header ;
StartBlock ( & header ) ;
Q_strncpy ( szBlockName , StringFromHeaderSymbol ( header . symbol ) , SIZE_BLOCK_NAME_BUF ) ;
}
//-------------------------------------
void CRestore : : StartBlock ( )
{
char szBlockName [ SIZE_BLOCK_NAME_BUF ] ;
StartBlock ( szBlockName ) ;
}
//-------------------------------------
void CRestore : : EndBlock ( )
{
int endPos = m_BlockEndStack [ m_BlockEndStack . Count ( ) - 1 ] ;
m_BlockEndStack . Remove ( m_BlockEndStack . Count ( ) - 1 ) ;
SetReadPos ( endPos ) ;
}
//-------------------------------------
int CRestore : : ReadFields ( const char * pname , void * pBaseData , datamap_t * pRootMap , typedescription_t * pFields , int fieldCount )
{
static int lastName = - 1 ;
Verify ( ReadShort ( ) = = sizeof ( int ) ) ; // First entry should be an int
int symName = m_pData - > FindCreateSymbol ( pname ) ;
// Check the struct name
int curSym = ReadShort ( ) ;
if ( curSym ! = symName ) // Field Set marker
{
const char * pLastName = m_pData - > StringFromSymbol ( lastName ) ;
const char * pCurName = m_pData - > StringFromSymbol ( curSym ) ;
Msg ( " Expected %s found %s ( raw '%s' )! (prev: %s) \n " , pname , pCurName , BufferPointer ( ) , pLastName ) ;
Msg ( " Field type name may have changed or inheritance graph changed, save file is suspect \n " ) ;
m_pData - > Rewind ( 2 * sizeof ( short ) ) ;
return 0 ;
}
lastName = symName ;
// Clear out base data
EmptyFields ( pBaseData , pFields , fieldCount ) ;
// Skip over the struct name
int i ;
int nFieldsSaved = ReadInt ( ) ; // Read field count
int searchCookie = 0 ; // Make searches faster, most data is read/written in the same order
SaveRestoreRecordHeader_t header ;
for ( i = 0 ; i < nFieldsSaved ; i + + )
{
ReadHeader ( & header ) ;
typedescription_t * pField = FindField ( m_pData - > StringFromSymbol ( header . symbol ) , pFields , fieldCount , & searchCookie ) ;
if ( pField & & ShouldReadField ( pField ) )
{
ReadField ( header , ( ( char * ) pBaseData + pField - > fieldOffset [ TD_OFFSET_NORMAL ] ) , pRootMap , pField ) ;
}
else
{
BufferSkipBytes ( header . size ) ; // Advance to next field
}
}
return 1 ;
}
//-------------------------------------
void CRestore : : ReadHeader ( SaveRestoreRecordHeader_t * pheader )
{
if ( pheader ! = NULL )
{
Assert ( pheader ! = NULL ) ;
pheader - > size = ReadShort ( ) ; // Read field size
pheader - > symbol = ReadShort ( ) ; // Read field name token
}
else
{
BufferSkipBytes ( sizeof ( short ) * 2 ) ;
}
}
//-------------------------------------
short CRestore : : ReadShort ( void )
{
short tmp = 0 ;
BufferReadBytes ( ( char * ) & tmp , sizeof ( short ) ) ;
return tmp ;
}
//-------------------------------------
int CRestore : : ReadInt ( void )
{
int tmp = 0 ;
BufferReadBytes ( ( char * ) & tmp , sizeof ( int ) ) ;
return tmp ;
}
//-------------------------------------
// Purpose: Recursively restores all the classes in an object, in reverse order (top down)
// Output : int 0 on failure, 1 on success
int CRestore : : DoReadAll ( void * pLeafObject , datamap_t * pLeafMap , datamap_t * pCurMap )
{
// restore base classes first
if ( pCurMap - > baseMap )
{
int status = DoReadAll ( pLeafObject , pLeafMap , pCurMap - > baseMap ) ;
if ( ! status )
return status ;
}
return ReadFields ( pCurMap - > dataClassName , pLeafObject , pLeafMap , pCurMap - > dataDesc , pCurMap - > dataNumFields ) ;
}
//-------------------------------------
char * CRestore : : BufferPointer ( void )
{
if ( ! m_pData )
return NULL ;
return m_pData - > AccessCurPos ( ) ;
}
//-------------------------------------
void CRestore : : BufferReadBytes ( char * pOutput , int size )
{
Assert ( m_pData ! = NULL ) ;
if ( ! m_pData | | m_pData - > BytesAvailable ( ) = = 0 )
return ;
if ( ! m_pData - > Read ( pOutput , size ) )
{
Warning ( " Restore underflow! \n " ) ;
Assert ( 0 ) ;
}
}
//-------------------------------------
void CRestore : : BufferSkipBytes ( int bytes )
{
BufferReadBytes ( NULL , bytes ) ;
}
//-------------------------------------
int CRestore : : ReadShort ( short * pValue , int nElems , int nBytesAvailable )
{
return ReadSimple ( pValue , nElems , nBytesAvailable ) ;
}
//-------------------------------------
int CRestore : : ReadInt ( int * pValue , int nElems , int nBytesAvailable )
{
return ReadSimple ( pValue , nElems , nBytesAvailable ) ;
}
//-------------------------------------
int CRestore : : ReadBool ( bool * pValue , int nElems , int nBytesAvailable )
{
COMPILE_TIME_ASSERT ( sizeof ( bool ) = = sizeof ( char ) ) ;
return ReadSimple ( pValue , nElems , nBytesAvailable ) ;
}
//-------------------------------------
int CRestore : : ReadFloat ( float * pValue , int nElems , int nBytesAvailable )
{
return ReadSimple ( pValue , nElems , nBytesAvailable ) ;
}
//-------------------------------------
int CRestore : : ReadData ( char * pData , int size , int nBytesAvailable )
{
return ReadSimple ( pData , size , nBytesAvailable ) ;
}
//-------------------------------------
void CRestore : : ReadString ( char * pDest , int nSizeDest , int nBytesAvailable )
{
const char * pString = BufferPointer ( ) ;
if ( ! nBytesAvailable )
nBytesAvailable = strlen ( pString ) + 1 ;
BufferSkipBytes ( nBytesAvailable ) ;
Q_strncpy ( pDest , pString , nSizeDest ) ;
}
//-------------------------------------
int CRestore : : ReadString ( string_t * pValue , int nElems , int nBytesAvailable )
{
AssertMsg ( nBytesAvailable > 0 , " CRestore::ReadString() implementation does not currently support unspecified bytes available " ) ;
int i ;
char * pString = BufferPointer ( ) ;
char * pLimit = pString + nBytesAvailable ;
for ( i = 0 ; i < nElems & & pString < pLimit ; i + + )
{
if ( * ( ( char * ) pString ) = = 0 )
pValue [ i ] = NULL_STRING ;
else
pValue [ i ] = AllocPooledString ( ( char * ) pString ) ;
while ( * pString )
pString + + ;
pString + + ;
}
BufferSkipBytes ( nBytesAvailable ) ;
return i ;
}
//-------------------------------------
int CRestore : : ReadVector ( Vector * pValue )
{
BufferReadBytes ( ( char * ) pValue , sizeof ( Vector ) ) ;
return 1 ;
}
//-------------------------------------
int CRestore : : ReadVector ( Vector * pValue , int nElems , int nBytesAvailable )
{
return ReadSimple ( pValue , nElems , nBytesAvailable ) ;
}
int CRestore : : ReadQuaternion ( Quaternion * pValue )
{
BufferReadBytes ( ( char * ) pValue , sizeof ( Quaternion ) ) ;
return 1 ;
}
//-------------------------------------
int CRestore : : ReadQuaternion ( Quaternion * pValue , int nElems , int nBytesAvailable )
{
return ReadSimple ( pValue , nElems , nBytesAvailable ) ;
}
//-------------------------------------
int CRestore : : ReadVMatrix ( VMatrix * pValue , int nElems , int nBytesAvailable )
{
return ReadSimple ( pValue , nElems , nBytesAvailable ) ;
}
int CRestore : : ReadVMatrixWorldspace ( VMatrix * pValue , int nElems , int nBytesAvailable )
{
Vector basePosition = m_pGameInfo - > GetLandmark ( ) ;
VMatrix tmp ;
for ( int i = 0 ; i < nElems ; i + + )
{
BufferReadBytes ( ( char * ) & tmp , sizeof ( float ) * 16 ) ;
VMatrixOffset ( pValue [ i ] , tmp , basePosition ) ;
}
return nElems ;
}
int CRestore : : ReadMatrix3x4Worldspace ( matrix3x4_t * pValue , int nElems , int nBytesAvailable )
{
Vector basePosition = m_pGameInfo - > GetLandmark ( ) ;
matrix3x4_t tmp ;
for ( int i = 0 ; i < nElems ; i + + )
{
BufferReadBytes ( ( char * ) & tmp , sizeof ( matrix3x4_t ) ) ;
Matrix3x4Offset ( pValue [ i ] , tmp , basePosition ) ;
}
return nElems ;
}
int CRestore : : ReadInterval ( interval_t * interval , int count , int nBytesAvailable )
{
return ReadSimple ( interval , count , nBytesAvailable ) ;
}
//---------------------------------------------------------
//
// Game centric restore methods
//
CBaseEntity * CRestore : : EntityFromIndex ( int entityIndex )
{
if ( ! m_pGameInfo | | entityIndex < 0 )
return NULL ;
int i ;
entitytable_t * pTable ;
for ( i = 0 ; i < m_pGameInfo - > NumEntities ( ) ; i + + )
{
pTable = m_pGameInfo - > GetEntityInfo ( i ) ;
if ( pTable - > id = = entityIndex )
return pTable - > hEnt ;
}
return NULL ;
}
//-------------------------------------
int CRestore : : ReadEntityPtr ( CBaseEntity * * ppEntity , int count , int nBytesAvailable )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
int nRead = ReadInt ( entityArray , count , nBytesAvailable ) ;
for ( int i = 0 ; i < nRead ; i + + ) // nRead is never greater than count
{
ppEntity [ i ] = EntityFromIndex ( entityArray [ i ] ) ;
}
if ( nRead < count )
{
memset ( & ppEntity [ nRead ] , 0 , ( count - nRead ) * sizeof ( ppEntity [ 0 ] ) ) ;
}
return nRead ;
}
//-------------------------------------
int CRestore : : ReadEdictPtr ( edict_t * * ppEdict , int count , int nBytesAvailable )
{
# if !defined( CLIENT_DLL )
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
CBaseEntity * pEntity ;
int nRead = ReadInt ( entityArray , count , nBytesAvailable ) ;
for ( int i = 0 ; i < nRead ; i + + ) // nRead is never greater than count
{
pEntity = EntityFromIndex ( entityArray [ i ] ) ;
ppEdict [ i ] = ( pEntity ) ? pEntity - > edict ( ) : NULL ;
}
if ( nRead < count )
{
memset ( & ppEdict [ nRead ] , 0 , ( count - nRead ) * sizeof ( ppEdict [ 0 ] ) ) ;
}
return nRead ;
# else
return 0 ;
# endif
}
//-------------------------------------
int CRestore : : ReadEHandle ( EHANDLE * pEHandle , int count , int nBytesAvailable )
{
AssertMsg ( count < = MAX_ENTITYARRAY , " Array of entities or ehandles exceeds limit supported by save/restore " ) ;
int entityArray [ MAX_ENTITYARRAY ] ;
int nRead = ReadInt ( entityArray , count , nBytesAvailable ) ;
for ( int i = 0 ; i < nRead ; i + + ) // nRead is never greater than count
{
pEHandle [ i ] = EntityFromIndex ( entityArray [ i ] ) ;
}
if ( nRead < count )
{
memset ( & pEHandle [ nRead ] , 0xFF , ( count - nRead ) * sizeof ( pEHandle [ 0 ] ) ) ;
}
return nRead ;
}
//-------------------------------------
// Purpose: Reads all the fields that are not client neutral. In the event of
// a librarization of save/restore, these would NOT reside in the library
void CRestore : : ReadGameField ( const SaveRestoreRecordHeader_t & header , void * pDest , datamap_t * pRootMap , typedescription_t * pField )
{
switch ( pField - > fieldType )
{
case FIELD_POSITION_VECTOR :
{
ReadPositionVector ( ( Vector * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_TIME :
{
ReadTime ( ( float * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_TICK :
{
ReadTick ( ( int * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_FUNCTION :
{
ReadFunction ( pRootMap , ( inputfunc_t * * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_MODELINDEX :
{
int * pModelIndex = ( int * ) pDest ;
string_t * pModelName = ( string_t * ) stackalloc ( pField - > fieldSize * sizeof ( string_t ) ) ;
int nRead = ReadString ( pModelName , pField - > fieldSize , header . size ) ;
for ( int i = 0 ; i < nRead ; i + + )
{
if ( pModelName [ i ] = = NULL_STRING )
{
pModelIndex [ i ] = - 1 ;
continue ;
}
pModelIndex [ i ] = modelinfo - > GetModelIndex ( STRING ( pModelName [ i ] ) ) ;
# if !defined( CLIENT_DLL )
if ( m_precache )
{
CBaseEntity : : PrecacheModel ( STRING ( pModelName [ i ] ) ) ;
}
# endif
}
break ;
}
case FIELD_MATERIALINDEX :
{
int * pMaterialIndex = ( int * ) pDest ;
string_t * pMaterialName = ( string_t * ) stackalloc ( pField - > fieldSize * sizeof ( string_t ) ) ;
int nRead = ReadString ( pMaterialName , pField - > fieldSize , header . size ) ;
for ( int i = 0 ; i < nRead ; i + + )
{
if ( pMaterialName [ i ] = = NULL_STRING )
{
pMaterialIndex [ i ] = 0 ;
continue ;
}
pMaterialIndex [ i ] = GetMaterialIndex ( STRING ( pMaterialName [ i ] ) ) ;
# if !defined( CLIENT_DLL )
if ( m_precache )
{
PrecacheMaterial ( STRING ( pMaterialName [ i ] ) ) ;
}
# endif
}
break ;
}
case FIELD_MODELNAME :
case FIELD_SOUNDNAME :
{
string_t * pStringDest = ( string_t * ) pDest ;
int nRead = ReadString ( pStringDest , pField - > fieldSize , header . size ) ;
if ( m_precache )
{
# if !defined( CLIENT_DLL )
// HACKHACK: Rewrite the .bsp models to match the map name in case the bugreporter renamed it
if ( pField - > fieldType = = FIELD_MODELNAME & & Q_stristr ( pStringDest - > ToCStr ( ) , " .bsp " ) )
{
char buf [ MAX_PATH ] ;
Q_strncpy ( buf , " maps/ " , sizeof ( buf ) ) ;
Q_strncat ( buf , gpGlobals - > mapname . ToCStr ( ) , sizeof ( buf ) ) ;
Q_strncat ( buf , " .bsp " , sizeof ( buf ) ) ;
* pStringDest = AllocPooledString ( buf ) ;
}
# endif
for ( int i = 0 ; i < nRead ; i + + )
{
if ( pStringDest [ i ] ! = NULL_STRING )
{
# if !defined( CLIENT_DLL )
if ( pField - > fieldType = = FIELD_MODELNAME )
{
CBaseEntity : : PrecacheModel ( STRING ( pStringDest [ i ] ) ) ;
}
else if ( pField - > fieldType = = FIELD_SOUNDNAME )
{
CBaseEntity : : PrecacheScriptSound ( STRING ( pStringDest [ i ] ) ) ;
}
# endif
}
}
}
break ;
}
case FIELD_CLASSPTR :
ReadEntityPtr ( ( CBaseEntity * * ) pDest , pField - > fieldSize , header . size ) ;
break ;
case FIELD_EDICT :
# if !defined( CLIENT_DLL )
ReadEdictPtr ( ( edict_t * * ) pDest , pField - > fieldSize , header . size ) ;
# else
Assert ( ! " FIELD_EDICT not valid for client .dll " ) ;
# endif
break ;
case FIELD_EHANDLE :
ReadEHandle ( ( EHANDLE * ) pDest , pField - > fieldSize , header . size ) ;
break ;
case FIELD_VMATRIX :
{
ReadVMatrix ( ( VMatrix * ) pDest , pField - > fieldSize , header . size ) ;
break ;
}
case FIELD_VMATRIX_WORLDSPACE :
ReadVMatrixWorldspace ( ( VMatrix * ) pDest , pField - > fieldSize , header . size ) ;
break ;
case FIELD_MATRIX3X4_WORLDSPACE :
ReadMatrix3x4Worldspace ( ( matrix3x4_t * ) pDest , pField - > fieldSize , header . size ) ;
break ;
case FIELD_INTERVAL :
ReadInterval ( ( interval_t * ) pDest , pField - > fieldSize , header . size ) ;
break ;
default :
Warning ( " Bad field type \n " ) ;
Assert ( 0 ) ;
}
}
//-------------------------------------
int CRestore : : ReadTime ( float * pValue , int count , int nBytesAvailable )
{
float baseTime = m_pGameInfo - > GetBaseTime ( ) ;
int nRead = ReadFloat ( pValue , count , nBytesAvailable ) ;
for ( int i = nRead - 1 ; i > = 0 ; i - - )
{
if ( pValue [ i ] = = ZERO_TIME )
pValue [ i ] = 0.0 ;
else if ( pValue [ i ] ! = INVALID_TIME & & pValue [ i ] ! = FLT_MAX )
pValue [ i ] + = baseTime ;
}
return nRead ;
}
int CRestore : : ReadTick ( int * pValue , int count , int nBytesAvailable )
{
// HACK HACK: Adding 0.1f here makes sure that all tick times read
// from .sav file which are near the basetime will end up just ahead of
// the base time, because we are restoring we'll have a slow frame of the
// max frametime of 0.1 seconds and that could otherwise cause all of our
// think times to get synchronized to each other... sigh. ywb...
int baseTick = TIME_TO_TICKS ( m_pGameInfo - > GetBaseTime ( ) + 0.1f ) ;
int nRead = ReadInt ( pValue , count , nBytesAvailable ) ;
for ( int i = nRead - 1 ; i > = 0 ; i - - )
{
if ( pValue [ i ] ! = TICK_NEVER_THINK_ENCODE )
{
// Rebase it
pValue [ i ] + = baseTick ;
}
else
{
// Slam to -1 value
pValue [ i ] = TICK_NEVER_THINK ;
}
}
return nRead ;
}
//-------------------------------------
int CRestore : : ReadPositionVector ( Vector * pValue )
{
return ReadPositionVector ( pValue , 1 , sizeof ( Vector ) ) ;
}
//-------------------------------------
int CRestore : : ReadPositionVector ( Vector * pValue , int count , int nBytesAvailable )
{
Vector basePosition = m_pGameInfo - > GetLandmark ( ) ;
int nRead = ReadVector ( pValue , count , nBytesAvailable ) ;
for ( int i = nRead - 1 ; i > = 0 ; i - - )
{
if ( pValue [ i ] ! = vec3_invalid )
pValue [ i ] + = basePosition ;
}
return nRead ;
}
//-------------------------------------
int CRestore : : ReadFunction ( datamap_t * pMap , inputfunc_t * * pValue , int count , int nBytesAvailable )
{
AssertMsg ( nBytesAvailable > 0 , " CRestore::ReadFunction() implementation does not currently support unspecified bytes available " ) ;
char * pszFunctionName = BufferPointer ( ) ;
BufferSkipBytes ( nBytesAvailable ) ;
AssertMsg ( count = = 1 , " Arrays of functions not presently supported " ) ;
if ( * pszFunctionName = = 0 )
* pValue = NULL ;
else
{
inputfunc_t func = UTIL_FunctionFromName ( pMap , pszFunctionName ) ;
# ifdef GNUC
Q_memcpy ( ( void * ) pValue , & func , sizeof ( void * ) * 2 ) ;
# else
Q_memcpy ( ( void * ) pValue , & func , sizeof ( void * ) ) ;
# endif
}
return 0 ;
}
//-----------------------------------------------------------------------------
//
// Entity data saving routines
//
//-----------------------------------------------------------------------------
BEGIN_SIMPLE_DATADESC ( entitytable_t )
DEFINE_FIELD ( id , FIELD_INTEGER ) ,
DEFINE_FIELD ( edictindex , FIELD_INTEGER ) ,
DEFINE_FIELD ( saveentityindex , FIELD_INTEGER ) ,
// DEFINE_FIELD( restoreentityindex, FIELD_INTEGER ),
// hEnt (not saved, this is the fixup)
DEFINE_FIELD ( location , FIELD_INTEGER ) ,
DEFINE_FIELD ( size , FIELD_INTEGER ) ,
DEFINE_FIELD ( flags , FIELD_INTEGER ) ,
DEFINE_FIELD ( classname , FIELD_STRING ) ,
DEFINE_FIELD ( globalname , FIELD_STRING ) ,
DEFINE_FIELD ( landmarkModelSpace , FIELD_VECTOR ) ,
DEFINE_FIELD ( modelname , FIELD_STRING ) ,
END_DATADESC ( )
//-----------------------------------------------------------------------------
// Utilities entities can use when saving
//-----------------------------------------------------------------------------
class CEntitySaveUtils : public IEntitySaveUtils
{
public :
// Call these in pre-save + post save
void PreSave ( ) ;
void PostSave ( ) ;
// Methods of IEntitySaveUtils
virtual void AddLevelTransitionSaveDependency ( CBaseEntity * pEntity1 , CBaseEntity * pEntity2 ) ;
virtual int GetEntityDependencyCount ( CBaseEntity * pEntity ) ;
virtual int GetEntityDependencies ( CBaseEntity * pEntity , int nCount , CBaseEntity * * ppEntList ) ;
private :
IPhysicsObjectPairHash * m_pLevelAdjacencyDependencyHash ;
} ;
//-----------------------------------------------------------------------------
// Call these in pre-save + post save
//-----------------------------------------------------------------------------
void CEntitySaveUtils : : PreSave ( )
{
Assert ( ! m_pLevelAdjacencyDependencyHash ) ;
MEM_ALLOC_CREDIT ( ) ;
m_pLevelAdjacencyDependencyHash = physics - > CreateObjectPairHash ( ) ;
}
void CEntitySaveUtils : : PostSave ( )
{
physics - > DestroyObjectPairHash ( m_pLevelAdjacencyDependencyHash ) ;
m_pLevelAdjacencyDependencyHash = NULL ;
}
//-----------------------------------------------------------------------------
// Gets the # of dependencies for a particular entity
//-----------------------------------------------------------------------------
int CEntitySaveUtils : : GetEntityDependencyCount ( CBaseEntity * pEntity )
{
return m_pLevelAdjacencyDependencyHash - > GetPairCountForObject ( pEntity ) ;
}
//-----------------------------------------------------------------------------
// Gets all dependencies for a particular entity
//-----------------------------------------------------------------------------
int CEntitySaveUtils : : GetEntityDependencies ( CBaseEntity * pEntity , int nCount , CBaseEntity * * ppEntList )
{
return m_pLevelAdjacencyDependencyHash - > GetPairListForObject ( pEntity , nCount , ( void * * ) ppEntList ) ;
}
//-----------------------------------------------------------------------------
// Methods of IEntitySaveUtils
//-----------------------------------------------------------------------------
void CEntitySaveUtils : : AddLevelTransitionSaveDependency ( CBaseEntity * pEntity1 , CBaseEntity * pEntity2 )
{
if ( pEntity1 ! = pEntity2 )
{
m_pLevelAdjacencyDependencyHash - > AddObjectPair ( pEntity1 , pEntity2 ) ;
}
}
//-----------------------------------------------------------------------------
// Block handler for save/restore of entities
//-----------------------------------------------------------------------------
class CEntitySaveRestoreBlockHandler : public ISaveRestoreBlockHandler
{
public :
const char * GetBlockName ( ) ;
void PreSave ( CSaveRestoreData * pSaveData ) ;
void Save ( ISave * pSave ) ;
void WriteSaveHeaders ( ISave * pSave ) ;
virtual void PostSave ( ) ;
virtual void PreRestore ( ) ;
void ReadRestoreHeaders ( IRestore * pRestore ) ;
void Restore ( IRestore * pRestore , bool createPlayers ) ;
virtual void PostRestore ( ) ;
inline IEntitySaveUtils * GetEntitySaveUtils ( ) { return & m_EntitySaveUtils ; }
private :
friend int CreateEntityTransitionList ( CSaveRestoreData * pSaveData , int levelMask ) ;
bool SaveInitEntities ( CSaveRestoreData * pSaveData ) ;
bool DoRestoreEntity ( CBaseEntity * pEntity , IRestore * pRestore ) ;
Vector ModelSpaceLandmark ( int modelIndex ) ;
int RestoreEntity ( CBaseEntity * pEntity , IRestore * pRestore , entitytable_t * pEntInfo ) ;
# if !defined( CLIENT_DLL )
// Find the matching global entity. Spit out an error if the designer made entities of
// different classes with the same global name
CBaseEntity * FindGlobalEntity ( string_t classname , string_t globalname ) ;
int RestoreGlobalEntity ( CBaseEntity * pEntity , CSaveRestoreData * pSaveData , entitytable_t * pEntInfo ) ;
# endif
private :
CEntitySaveUtils m_EntitySaveUtils ;
} ;
//-----------------------------------------------------------------------------
CEntitySaveRestoreBlockHandler g_EntitySaveRestoreBlockHandler ;
//-------------------------------------
ISaveRestoreBlockHandler * GetEntitySaveRestoreBlockHandler ( )
{
return & g_EntitySaveRestoreBlockHandler ;
}
IEntitySaveUtils * GetEntitySaveUtils ( )
{
return g_EntitySaveRestoreBlockHandler . GetEntitySaveUtils ( ) ;
}
//-----------------------------------------------------------------------------
// Implementation of the block handler for save/restore of entities
//-----------------------------------------------------------------------------
const char * CEntitySaveRestoreBlockHandler : : GetBlockName ( )
{
return " Entities " ;
}
//---------------------------------
void CEntitySaveRestoreBlockHandler : : PreSave ( CSaveRestoreData * pSaveData )
{
MDLCACHE_CRITICAL_SECTION ( ) ;
IGameSystem : : OnSaveAllSystems ( ) ;
m_EntitySaveUtils . PreSave ( ) ;
// Allow the entities to do some work
CBaseEntity * pEnt = NULL ;
# if !defined( CLIENT_DLL )
while ( ( pEnt = gEntList . NextEnt ( pEnt ) ) ! = NULL )
{
pEnt - > OnSave ( & m_EntitySaveUtils ) ;
}
# else
// Do this because it'll force entities to figure out their origins, and that requires
// SetupBones in the case of aiments.
{
C_BaseAnimating : : AutoAllowBoneAccess boneaccess ( true , true ) ;
int last = ClientEntityList ( ) . GetHighestEntityIndex ( ) ;
ClientEntityHandle_t iter = ClientEntityList ( ) . FirstHandle ( ) ;
for ( int e = 0 ; e < = last ; e + + )
{
pEnt = ClientEntityList ( ) . GetBaseEntity ( e ) ;
if ( ! pEnt )
continue ;
pEnt - > OnSave ( ) ;
}
while ( iter ! = ClientEntityList ( ) . InvalidHandle ( ) )
{
pEnt = ClientEntityList ( ) . GetBaseEntityFromHandle ( iter ) ;
if ( pEnt & & pEnt - > ObjectCaps ( ) & FCAP_SAVE_NON_NETWORKABLE )
{
pEnt - > OnSave ( ) ;
}
iter = ClientEntityList ( ) . NextHandle ( iter ) ;
}
}
# endif
SaveInitEntities ( pSaveData ) ;
}
//---------------------------------
void CEntitySaveRestoreBlockHandler : : Save ( ISave * pSave )
{
CGameSaveRestoreInfo * pSaveData = pSave - > GetGameSaveRestoreInfo ( ) ;
// write entity list that was previously built by SaveInitEntities()
for ( int i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
entitytable_t * pEntInfo = pSaveData - > GetEntityInfo ( i ) ;
pEntInfo - > location = pSave - > GetWritePos ( ) ;
pEntInfo - > size = 0 ;
CBaseEntity * pEnt = pEntInfo - > hEnt ;
if ( pEnt & & ! ( pEnt - > ObjectCaps ( ) & FCAP_DONT_SAVE ) )
{
MDLCACHE_CRITICAL_SECTION ( ) ;
# if !defined( CLIENT_DLL )
AssertMsg ( ! pEnt - > edict ( ) | | ( pEnt - > m_iClassname ! = NULL_STRING & &
( STRING ( pEnt - > m_iClassname ) [ 0 ] ! = 0 ) & &
FStrEq ( STRING ( pEnt - > m_iClassname ) , pEnt - > GetClassname ( ) ) ) ,
" Saving entity with invalid classname " ) ;
# endif
pSaveData - > SetCurrentEntityContext ( pEnt ) ;
pEnt - > Save ( * pSave ) ;
pSaveData - > SetCurrentEntityContext ( NULL ) ;
pEntInfo - > size = pSave - > GetWritePos ( ) - pEntInfo - > location ; // Size of entity block is data size written to block
pEntInfo - > classname = pEnt - > m_iClassname ; // Remember entity class for respawn
# if !defined( CLIENT_DLL )
pEntInfo - > globalname = pEnt - > m_iGlobalname ; // remember global name
pEntInfo - > landmarkModelSpace = ModelSpaceLandmark ( pEnt - > GetModelIndex ( ) ) ;
int nEntIndex = pEnt - > edict ( ) ? ENTINDEX ( pEnt - > edict ( ) ) : - 1 ;
bool bIsPlayer = ( ( nEntIndex > = 1 ) & & ( nEntIndex < = gpGlobals - > maxClients ) ) ? true : false ;
if ( bIsPlayer )
{
pEntInfo - > flags | = FENTTABLE_PLAYER ;
}
# endif
}
}
}
//---------------------------------
void CEntitySaveRestoreBlockHandler : : WriteSaveHeaders ( ISave * pSave )
{
CGameSaveRestoreInfo * pSaveData = pSave - > GetGameSaveRestoreInfo ( ) ;
int nEntities = pSaveData - > NumEntities ( ) ;
pSave - > WriteInt ( & nEntities ) ;
for ( int i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
pSave - > WriteFields ( " ETABLE " , pSaveData - > GetEntityInfo ( i ) , NULL , entitytable_t : : m_DataMap . dataDesc , entitytable_t : : m_DataMap . dataNumFields ) ;
}
//---------------------------------
void CEntitySaveRestoreBlockHandler : : PostSave ( )
{
m_EntitySaveUtils . PostSave ( ) ;
}
//---------------------------------
void CEntitySaveRestoreBlockHandler : : PreRestore ( )
{
}
//---------------------------------
void CEntitySaveRestoreBlockHandler : : ReadRestoreHeaders ( IRestore * pRestore )
{
CGameSaveRestoreInfo * pSaveData = pRestore - > GetGameSaveRestoreInfo ( ) ;
int nEntities ;
pRestore - > ReadInt ( & nEntities ) ;
entitytable_t * pEntityTable = ( entitytable_t * ) engine - > SaveAllocMemory ( ( sizeof ( entitytable_t ) * nEntities ) , sizeof ( char ) ) ;
if ( ! pEntityTable )
{
return ;
}
pSaveData - > InitEntityTable ( pEntityTable , nEntities ) ;
for ( int i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
pRestore - > ReadFields ( " ETABLE " , pSaveData - > GetEntityInfo ( i ) , NULL , entitytable_t : : m_DataMap . dataDesc , entitytable_t : : m_DataMap . dataNumFields ) ;
}
//---------------------------------
# if !defined( CLIENT_DLL )
void CEntitySaveRestoreBlockHandler : : Restore ( IRestore * pRestore , bool createPlayers )
{
entitytable_t * pEntInfo ;
CBaseEntity * pent ;
CGameSaveRestoreInfo * pSaveData = pRestore - > GetGameSaveRestoreInfo ( ) ;
bool restoredWorld = false ;
// Create entity list
int i ;
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
pEntInfo = pSaveData - > GetEntityInfo ( i ) ;
if ( pEntInfo - > classname ! = NULL_STRING & & pEntInfo - > size & & ! ( pEntInfo - > flags & FENTTABLE_REMOVED ) )
{
if ( pEntInfo - > edictindex = = 0 ) // worldspawn
{
Assert ( i = = 0 ) ;
pent = CreateEntityByName ( STRING ( pEntInfo - > classname ) ) ;
pRestore - > SetReadPos ( pEntInfo - > location ) ;
if ( RestoreEntity ( pent , pRestore , pEntInfo ) < 0 )
{
pEntInfo - > hEnt = NULL ;
pEntInfo - > restoreentityindex = - 1 ;
UTIL_RemoveImmediate ( pent ) ;
}
else
{
// force the entity to be relinked
AddRestoredEntity ( pent ) ;
}
}
else if ( ( pEntInfo - > edictindex > 0 ) & & ( pEntInfo - > edictindex < = gpGlobals - > maxClients ) )
{
if ( ! ( pEntInfo - > flags & FENTTABLE_PLAYER ) )
{
Warning ( " ENTITY IS NOT A PLAYER: %d \n " , i ) ;
Assert ( 0 ) ;
}
edict_t * ed = INDEXENT ( pEntInfo - > edictindex ) ;
if ( ed & & createPlayers )
{
// create the player
pent = CBasePlayer : : CreatePlayer ( STRING ( pEntInfo - > classname ) , ed ) ;
}
else
pent = NULL ;
}
else
{
pent = CreateEntityByName ( STRING ( pEntInfo - > classname ) ) ;
}
pEntInfo - > hEnt = pent ;
pEntInfo - > restoreentityindex = pent ? pent - > entindex ( ) : - 1 ;
if ( pent & & pEntInfo - > restoreentityindex = = 0 )
{
if ( ! FClassnameIs ( pent , " worldspawn " ) )
{
pEntInfo - > restoreentityindex = - 1 ;
}
}
if ( pEntInfo - > restoreentityindex = = 0 )
{
Assert ( ! restoredWorld ) ;
restoredWorld = true ;
}
}
else
{
pEntInfo - > hEnt = NULL ;
pEntInfo - > restoreentityindex = - 1 ;
}
}
// Now spawn entities
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
pEntInfo = pSaveData - > GetEntityInfo ( i ) ;
if ( pEntInfo - > edictindex ! = 0 )
{
pent = pEntInfo - > hEnt ;
pRestore - > SetReadPos ( pEntInfo - > location ) ;
if ( pent )
{
if ( RestoreEntity ( pent , pRestore , pEntInfo ) < 0 )
{
pEntInfo - > hEnt = NULL ;
pEntInfo - > restoreentityindex = - 1 ;
UTIL_RemoveImmediate ( pent ) ;
}
else
{
AddRestoredEntity ( pent ) ;
}
}
}
}
}
# else // CLIENT DLL VERSION
void CEntitySaveRestoreBlockHandler : : Restore ( IRestore * pRestore , bool createPlayers )
{
entitytable_t * pEntInfo ;
CBaseEntity * pent ;
CGameSaveRestoreInfo * pSaveData = pRestore - > GetGameSaveRestoreInfo ( ) ;
// Create entity list
int i ;
bool restoredWorld = false ;
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
pEntInfo = pSaveData - > GetEntityInfo ( i ) ;
pent = ClientEntityList ( ) . GetBaseEntity ( pEntInfo - > restoreentityindex ) ;
pEntInfo - > hEnt = pent ;
}
// Blast saved data into entities
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
pEntInfo = pSaveData - > GetEntityInfo ( i ) ;
bool bRestoredCorrectly = false ;
// FIXME, need to translate save spot to real index here using lookup table transmitted from server
//Assert( !"Need translation still" );
if ( pEntInfo - > restoreentityindex > = 0 )
{
if ( pEntInfo - > restoreentityindex = = 0 )
{
Assert ( ! restoredWorld ) ;
restoredWorld = true ;
}
pent = ClientEntityList ( ) . GetBaseEntity ( pEntInfo - > restoreentityindex ) ;
pRestore - > SetReadPos ( pEntInfo - > location ) ;
if ( pent )
{
if ( RestoreEntity ( pent , pRestore , pEntInfo ) > = 0 )
{
// Call the OnRestore method
AddRestoredEntity ( pent ) ;
bRestoredCorrectly = true ;
}
}
}
// BUGBUG: JAY: Disable ragdolls across transitions until PVS/solid check & client entity patch file are implemented
else if ( ! pSaveData - > levelInfo . fUseLandmark )
{
if ( pEntInfo - > classname ! = NULL_STRING )
{
pent = CreateEntityByName ( STRING ( pEntInfo - > classname ) ) ;
pent - > InitializeAsClientEntity ( NULL , RENDER_GROUP_OPAQUE_ENTITY ) ;
pRestore - > SetReadPos ( pEntInfo - > location ) ;
if ( pent )
{
if ( RestoreEntity ( pent , pRestore , pEntInfo ) > = 0 )
{
pEntInfo - > hEnt = pent ;
AddRestoredEntity ( pent ) ;
bRestoredCorrectly = true ;
}
}
}
}
if ( ! bRestoredCorrectly )
{
pEntInfo - > hEnt = NULL ;
pEntInfo - > restoreentityindex = - 1 ;
}
}
// Note, server does this after local player connects fully
IGameSystem : : OnRestoreAllSystems ( ) ;
// Tell hud elements to modify behavior based on game restoration, if applicable
gHUD . OnRestore ( ) ;
}
# endif
void CEntitySaveRestoreBlockHandler : : PostRestore ( )
{
}
void SaveEntityOnTable ( CBaseEntity * pEntity , CSaveRestoreData * pSaveData , int & iSlot )
{
entitytable_t * pEntInfo = pSaveData - > GetEntityInfo ( iSlot ) ;
pEntInfo - > id = iSlot ;
# if !defined( CLIENT_DLL )
pEntInfo - > edictindex = pEntity - > RequiredEdictIndex ( ) ;
# else
pEntInfo - > edictindex = - 1 ;
# endif
pEntInfo - > modelname = pEntity - > GetModelName ( ) ;
pEntInfo - > restoreentityindex = - 1 ;
pEntInfo - > saveentityindex = pEntity ? pEntity - > entindex ( ) : - 1 ;
pEntInfo - > hEnt = pEntity ;
pEntInfo - > flags = 0 ;
pEntInfo - > location = 0 ;
pEntInfo - > size = 0 ;
pEntInfo - > classname = NULL_STRING ;
iSlot + + ;
}
//---------------------------------
bool CEntitySaveRestoreBlockHandler : : SaveInitEntities ( CSaveRestoreData * pSaveData )
{
int number_of_entities ;
# if !defined( CLIENT_DLL )
number_of_entities = gEntList . NumberOfEntities ( ) ;
# else
number_of_entities = ClientEntityList ( ) . NumberOfEntities ( true ) ;
# endif
entitytable_t * pEntityTable = ( entitytable_t * ) engine - > SaveAllocMemory ( ( sizeof ( entitytable_t ) * number_of_entities ) , sizeof ( char ) ) ;
if ( ! pEntityTable )
return false ;
pSaveData - > InitEntityTable ( pEntityTable , number_of_entities ) ;
// build the table of entities
// this is used to turn pointers into savable indices
// build up ID numbers for each entity, for use in pointer conversions
// if an entity requires a certain edict number upon restore, save that as well
CBaseEntity * pEnt = NULL ;
int i = 0 ;
# if !defined( CLIENT_DLL )
while ( ( pEnt = gEntList . NextEnt ( pEnt ) ) ! = NULL )
{
# else
int last = ClientEntityList ( ) . GetHighestEntityIndex ( ) ;
for ( int e = 0 ; e < = last ; e + + )
{
pEnt = ClientEntityList ( ) . GetBaseEntity ( e ) ;
if ( ! pEnt )
continue ;
# endif
SaveEntityOnTable ( pEnt , pSaveData , i ) ;
}
# if defined( CLIENT_DLL )
ClientEntityHandle_t iter = ClientEntityList ( ) . FirstHandle ( ) ;
while ( iter ! = ClientEntityList ( ) . InvalidHandle ( ) )
{
pEnt = ClientEntityList ( ) . GetBaseEntityFromHandle ( iter ) ;
if ( pEnt & & pEnt - > ObjectCaps ( ) & FCAP_SAVE_NON_NETWORKABLE )
{
SaveEntityOnTable ( pEnt , pSaveData , i ) ;
}
iter = ClientEntityList ( ) . NextHandle ( iter ) ;
}
# endif
pSaveData - > BuildEntityHash ( ) ;
Assert ( i = = pSaveData - > NumEntities ( ) ) ;
return ( i = = pSaveData - > NumEntities ( ) ) ;
}
//---------------------------------
# if !defined( CLIENT_DLL )
// Find the matching global entity. Spit out an error if the designer made entities of
// different classes with the same global name
CBaseEntity * CEntitySaveRestoreBlockHandler : : FindGlobalEntity ( string_t classname , string_t globalname )
{
CBaseEntity * pReturn = NULL ;
while ( ( pReturn = gEntList . NextEnt ( pReturn ) ) ! = NULL )
{
if ( FStrEq ( STRING ( pReturn - > m_iGlobalname ) , STRING ( globalname ) ) )
break ;
}
if ( pReturn )
{
if ( ! FClassnameIs ( pReturn , STRING ( classname ) ) )
{
Warning ( " Global entity found %s, wrong class %s [expects class %s] \n " , STRING ( globalname ) , STRING ( pReturn - > m_iClassname ) , STRING ( classname ) ) ;
pReturn = NULL ;
}
}
return pReturn ;
}
# endif // !defined( CLIENT_DLL )
//---------------------------------
bool CEntitySaveRestoreBlockHandler : : DoRestoreEntity ( CBaseEntity * pEntity , IRestore * pRestore )
{
MDLCACHE_CRITICAL_SECTION ( ) ;
EHANDLE hEntity ;
hEntity = pEntity ;
pRestore - > GetGameSaveRestoreInfo ( ) - > SetCurrentEntityContext ( pEntity ) ;
pEntity - > Restore ( * pRestore ) ;
pRestore - > GetGameSaveRestoreInfo ( ) - > SetCurrentEntityContext ( NULL ) ;
# if !defined( CLIENT_DLL )
if ( pEntity - > ObjectCaps ( ) & FCAP_MUST_SPAWN )
{
pEntity - > Spawn ( ) ;
}
else
{
pEntity - > Precache ( ) ;
}
# endif
// Above calls may have resulted in self destruction
return ( hEntity ! = NULL ) ;
}
//---------------------------------
// Get a reference position in model space to compute
// changes in model space for global brush entities (designer models them in different coords!)
Vector CEntitySaveRestoreBlockHandler : : ModelSpaceLandmark ( int modelIndex )
{
const model_t * pModel = modelinfo - > GetModel ( modelIndex ) ;
if ( modelinfo - > GetModelType ( pModel ) ! = mod_brush )
return vec3_origin ;
Vector mins , maxs ;
modelinfo - > GetModelBounds ( pModel , mins , maxs ) ;
return mins ;
}
int CEntitySaveRestoreBlockHandler : : RestoreEntity ( CBaseEntity * pEntity , IRestore * pRestore , entitytable_t * pEntInfo )
{
if ( ! DoRestoreEntity ( pEntity , pRestore ) )
return 0 ;
# if !defined( CLIENT_DLL )
if ( pEntity - > m_iGlobalname ! = NULL_STRING )
{
int globalIndex = GlobalEntity_GetIndex ( pEntity - > m_iGlobalname ) ;
if ( globalIndex > = 0 )
{
// Already dead? delete
if ( GlobalEntity_GetState ( globalIndex ) = = GLOBAL_DEAD )
return - 1 ;
else if ( ! FStrEq ( STRING ( gpGlobals - > mapname ) , GlobalEntity_GetMap ( globalIndex ) ) )
{
pEntity - > MakeDormant ( ) ; // Hasn't been moved to this level yet, wait but stay alive
}
// In this level & not dead, continue on as normal
}
else
{
Warning ( " Global Entity %s (%s) not in table!!! \n " , STRING ( pEntity - > m_iGlobalname ) , STRING ( pEntity - > m_iClassname ) ) ;
// Spawned entities default to 'On'
GlobalEntity_Add ( pEntity - > m_iGlobalname , gpGlobals - > mapname , GLOBAL_ON ) ;
}
}
# endif
return 0 ;
}
//---------------------------------
# if !defined( CLIENT_DLL )
int CEntitySaveRestoreBlockHandler : : RestoreGlobalEntity ( CBaseEntity * pEntity , CSaveRestoreData * pSaveData , entitytable_t * pEntInfo )
{
Vector oldOffset ;
EHANDLE hEntitySafeHandle ;
hEntitySafeHandle = pEntity ;
oldOffset . Init ( ) ;
CRestore restoreHelper ( pSaveData ) ;
string_t globalName = pEntInfo - > globalname , className = pEntInfo - > classname ;
// -------------------
int globalIndex = GlobalEntity_GetIndex ( globalName ) ;
// Don't overlay any instance of the global that isn't the latest
// pSaveData->szCurrentMapName is the level this entity is coming from
// pGlobal->levelName is the last level the global entity was active in.
// If they aren't the same, then this global update is out of date.
if ( ! FStrEq ( pSaveData - > levelInfo . szCurrentMapName , GlobalEntity_GetMap ( globalIndex ) ) )
{
return 0 ;
}
// Compute the new global offset
CBaseEntity * pNewEntity = FindGlobalEntity ( className , globalName ) ;
if ( pNewEntity )
{
// Msg( "Overlay %s with %s\n", pNewEntity->GetClassname(), STRING(tmpEnt->classname) );
// Tell the restore code we're overlaying a global entity from another level
restoreHelper . SetGlobalMode ( 1 ) ; // Don't overwrite global fields
pSaveData - > modelSpaceOffset = pEntInfo - > landmarkModelSpace - ModelSpaceLandmark ( pNewEntity - > GetModelIndex ( ) ) ;
UTIL_Remove ( pEntity ) ;
pEntity = pNewEntity ; // we're going to restore this data OVER the old entity
pEntInfo - > hEnt = pEntity ;
// HACKHACK: Do we need system-wide support for removing non-global spawn allocated resources?
pEntity - > VPhysicsDestroyObject ( ) ;
Assert ( pEntInfo - > edictindex = = - 1 ) ;
// Update the global table to say that the global definition of this entity should come from this level
GlobalEntity_SetMap ( globalIndex , gpGlobals - > mapname ) ;
}
else
{
// This entity will be freed automatically by the engine-> If we don't do a restore on a matching entity (below)
// or call EntityUpdate() to move it to this level, we haven't changed global state at all.
DevMsg ( " Warning: No match for global entity %s found in destination level \n " , STRING ( globalName ) ) ;
return 0 ;
}
if ( ! DoRestoreEntity ( pEntity , & restoreHelper ) )
{
pEntity = NULL ;
}
// Is this an overriding global entity (coming over the transition)
pSaveData - > modelSpaceOffset . Init ( ) ;
if ( pEntity )
return 1 ;
return 0 ;
}
# endif // !defined( CLIENT_DLL )
//-----------------------------------------------------------------------------
CSaveRestoreData * SaveInit ( int size )
{
CSaveRestoreData * pSaveData ;
# if ( defined( CLIENT_DLL ) || defined( DISABLE_DEBUG_HISTORY ) )
if ( size < = 0 )
size = 2 * 1024 * 1024 ; // Reserve 2048K for now, UNDONE: Shrink this after compressing strings
# else
if ( size < = 0 )
size = 3 * 1024 * 1024 ; // Reserve 3096K for now, UNDONE: Shrink this after compressing strings
# endif
int numentities ;
# if !defined( CLIENT_DLL )
numentities = gEntList . NumberOfEntities ( ) ;
# else
numentities = ClientEntityList ( ) . NumberOfEntities ( ) ;
# endif
void * pSaveMemory = engine - > SaveAllocMemory ( sizeof ( CSaveRestoreData ) + ( sizeof ( entitytable_t ) * numentities ) + size , sizeof ( char ) ) ;
if ( ! pSaveMemory )
{
return NULL ;
}
pSaveData = MakeSaveRestoreData ( pSaveMemory ) ;
pSaveData - > Init ( ( char * ) ( pSaveData + 1 ) , size ) ; // skip the save structure
const int nTokens = 0xfff ; // Assume a maximum of 4K-1 symbol table entries(each of some length)
pSaveMemory = engine - > SaveAllocMemory ( nTokens , sizeof ( char * ) ) ;
if ( ! pSaveMemory )
{
engine - > SaveFreeMemory ( pSaveMemory ) ;
return NULL ;
}
pSaveData - > InitSymbolTable ( ( char * * ) pSaveMemory , nTokens ) ;
//---------------------------------
pSaveData - > levelInfo . time = gpGlobals - > curtime ; // Use DLL time
pSaveData - > levelInfo . vecLandmarkOffset = vec3_origin ;
pSaveData - > levelInfo . fUseLandmark = false ;
pSaveData - > levelInfo . connectionCount = 0 ;
//---------------------------------
gpGlobals - > pSaveData = pSaveData ;
return pSaveData ;
}
//-----------------------------------------------------------------------------
//
// ISaveRestoreBlockSet
//
// Purpose: Serves as holder for a group of sibling save sections. Takes
// care of iterating over them, making sure read points are
// queued up to the right spot (in case one section due to datadesc
// changes reads less than expected, or doesn't leave the
// read pointer at the right point), and ensuring the read pointer
// is at the end of the entire set when the set read is done.
//-----------------------------------------------------------------------------
struct SaveRestoreBlockHeader_t
{
char szName [ MAX_BLOCK_NAME_LEN + 1 ] ;
int locHeader ;
int locBody ;
DECLARE_SIMPLE_DATADESC ( ) ;
} ;
//-------------------------------------
class CSaveRestoreBlockSet : public ISaveRestoreBlockSet
{
public :
CSaveRestoreBlockSet ( const char * pszName )
{
Q_strncpy ( m_Name , pszName , sizeof ( m_Name ) ) ;
}
const char * GetBlockName ( )
{
return m_Name ;
}
//---------------------------------
void PreSave ( CSaveRestoreData * pData )
{
m_BlockHeaders . SetCount ( m_Handlers . Count ( ) ) ;
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
Q_strncpy ( m_BlockHeaders [ i ] . szName , m_Handlers [ i ] - > GetBlockName ( ) , MAX_BLOCK_NAME_LEN + 1 ) ;
m_Handlers [ i ] - > PreSave ( pData ) ;
}
}
void Save ( ISave * pSave )
{
int base = pSave - > GetWritePos ( ) ;
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
m_BlockHeaders [ i ] . locBody = pSave - > GetWritePos ( ) - base ;
m_Handlers [ i ] - > Save ( pSave ) ;
}
m_SizeBodies = pSave - > GetWritePos ( ) - base ;
}
void WriteSaveHeaders ( ISave * pSave )
{
int base = pSave - > GetWritePos ( ) ;
//
// Reserve space for a fully populated header
//
int dummyInt = - 1 ;
CUtlVector < SaveRestoreBlockHeader_t > dummyArr ;
dummyArr . SetCount ( m_BlockHeaders . Count ( ) ) ;
memset ( & dummyArr [ 0 ] , 0xff , dummyArr . Count ( ) * sizeof ( SaveRestoreBlockHeader_t ) ) ;
pSave - > WriteInt ( & dummyInt ) ; // size all headers
pSave - > WriteInt ( & dummyInt ) ; // size all bodies
SaveUtlVector ( pSave , & dummyArr , FIELD_EMBEDDED ) ;
//
// Write the data
//
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
m_BlockHeaders [ i ] . locHeader = pSave - > GetWritePos ( ) - base ;
m_Handlers [ i ] - > WriteSaveHeaders ( pSave ) ;
}
m_SizeHeaders = pSave - > GetWritePos ( ) - base ;
//
// Write the actual header
//
int savedPos = pSave - > GetWritePos ( ) ;
pSave - > SetWritePos ( base ) ;
pSave - > WriteInt ( & m_SizeHeaders ) ;
pSave - > WriteInt ( & m_SizeBodies ) ;
SaveUtlVector ( pSave , & m_BlockHeaders , FIELD_EMBEDDED ) ;
pSave - > SetWritePos ( savedPos ) ;
}
void PostSave ( )
{
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
m_Handlers [ i ] - > PostSave ( ) ;
}
m_BlockHeaders . Purge ( ) ;
}
//---------------------------------
void PreRestore ( )
{
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
m_Handlers [ i ] - > PreRestore ( ) ;
}
}
void ReadRestoreHeaders ( IRestore * pRestore )
{
int base = pRestore - > GetReadPos ( ) ;
pRestore - > ReadInt ( & m_SizeHeaders ) ;
pRestore - > ReadInt ( & m_SizeBodies ) ;
RestoreUtlVector ( pRestore , & m_BlockHeaders , FIELD_EMBEDDED ) ;
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
int location = GetBlockHeaderLoc ( m_Handlers [ i ] - > GetBlockName ( ) ) ;
if ( location ! = - 1 )
{
pRestore - > SetReadPos ( base + location ) ;
m_Handlers [ i ] - > ReadRestoreHeaders ( pRestore ) ;
}
}
pRestore - > SetReadPos ( base + m_SizeHeaders ) ;
}
void CallBlockHandlerRestore ( ISaveRestoreBlockHandler * pHandler , int baseFilePos , IRestore * pRestore , bool fCreatePlayers )
{
int location = GetBlockBodyLoc ( pHandler - > GetBlockName ( ) ) ;
if ( location ! = - 1 )
{
pRestore - > SetReadPos ( baseFilePos + location ) ;
pHandler - > Restore ( pRestore , fCreatePlayers ) ;
}
}
void Restore ( IRestore * pRestore , bool fCreatePlayers )
{
int base = pRestore - > GetReadPos ( ) ;
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
CallBlockHandlerRestore ( m_Handlers [ i ] , base , pRestore , fCreatePlayers ) ;
}
pRestore - > SetReadPos ( base + m_SizeBodies ) ;
}
void PostRestore ( )
{
for ( int i = 0 ; i < m_Handlers . Count ( ) ; i + + )
{
m_Handlers [ i ] - > PostRestore ( ) ;
}
m_BlockHeaders . Purge ( ) ;
}
//---------------------------------
void AddBlockHandler ( ISaveRestoreBlockHandler * pHandler )
{
// Grody, but... while this class is still isolated in saverestore.cpp, this seems like a fine time to assert:
AssertMsg ( pHandler = = & g_EntitySaveRestoreBlockHandler | | ( m_Handlers . Count ( ) > = 1 & & m_Handlers [ 0 ] = = & g_EntitySaveRestoreBlockHandler ) , " Expected entity save load to always be first " ) ;
Assert ( pHandler ! = this ) ;
m_Handlers . AddToTail ( pHandler ) ;
}
void RemoveBlockHandler ( ISaveRestoreBlockHandler * pHandler )
{
m_Handlers . FindAndRemove ( pHandler ) ;
}
//---------------------------------
private :
int GetBlockBodyLoc ( const char * pszName )
{
for ( int i = 0 ; i < m_BlockHeaders . Count ( ) ; i + + )
{
if ( strcmp ( m_BlockHeaders [ i ] . szName , pszName ) = = 0 )
return m_BlockHeaders [ i ] . locBody ;
}
return - 1 ;
}
int GetBlockHeaderLoc ( const char * pszName )
{
for ( int i = 0 ; i < m_BlockHeaders . Count ( ) ; i + + )
{
if ( strcmp ( m_BlockHeaders [ i ] . szName , pszName ) = = 0 )
return m_BlockHeaders [ i ] . locHeader ;
}
return - 1 ;
}
char m_Name [ MAX_BLOCK_NAME_LEN + 1 ] ;
CUtlVector < ISaveRestoreBlockHandler * > m_Handlers ;
int m_SizeHeaders ;
int m_SizeBodies ;
CUtlVector < SaveRestoreBlockHeader_t > m_BlockHeaders ;
} ;
//-------------------------------------
BEGIN_SIMPLE_DATADESC ( SaveRestoreBlockHeader_t )
DEFINE_ARRAY ( szName , FIELD_CHARACTER , MAX_BLOCK_NAME_LEN + 1 ) ,
DEFINE_FIELD ( locHeader , FIELD_INTEGER ) ,
DEFINE_FIELD ( locBody , FIELD_INTEGER ) ,
END_DATADESC ( )
//-------------------------------------
CSaveRestoreBlockSet g_SaveRestoreBlockSet ( " Game " ) ;
ISaveRestoreBlockSet * g_pGameSaveRestoreBlockSet = & g_SaveRestoreBlockSet ;
//=============================================================================
# if !defined( CLIENT_DLL )
//------------------------------------------------------------------------------
// Creates all entities that lie in the transition list
//------------------------------------------------------------------------------
void CreateEntitiesInTransitionList ( CSaveRestoreData * pSaveData , int levelMask )
{
CBaseEntity * pent ;
int i ;
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
entitytable_t * pEntInfo = pSaveData - > GetEntityInfo ( i ) ;
pEntInfo - > hEnt = NULL ;
if ( pEntInfo - > size = = 0 | | pEntInfo - > edictindex = = 0 )
continue ;
if ( pEntInfo - > classname = = NULL_STRING )
{
Warning ( " Entity with data saved, but with no classname \n " ) ;
Assert ( 0 ) ;
continue ;
}
bool active = ( pEntInfo - > flags & levelMask ) ? 1 : 0 ;
// spawn players
pent = NULL ;
if ( ( pEntInfo - > edictindex > 0 ) & & ( pEntInfo - > edictindex < = gpGlobals - > maxClients ) )
{
edict_t * ed = INDEXENT ( pEntInfo - > edictindex ) ;
if ( active & & ed & & ! ed - > IsFree ( ) )
{
if ( ! ( pEntInfo - > flags & FENTTABLE_PLAYER ) )
{
Warning ( " ENTITY IS NOT A PLAYER: %d \n " , i ) ;
Assert ( 0 ) ;
}
pent = CBasePlayer : : CreatePlayer ( STRING ( pEntInfo - > classname ) , ed ) ;
}
}
else if ( active )
{
pent = CreateEntityByName ( STRING ( pEntInfo - > classname ) ) ;
}
pEntInfo - > hEnt = pent ;
}
}
//-----------------------------------------------------------------------------
int CreateEntityTransitionList ( CSaveRestoreData * pSaveData , int levelMask )
{
CBaseEntity * pent ;
entitytable_t * pEntInfo ;
// Create entity list
CreateEntitiesInTransitionList ( pSaveData , levelMask ) ;
// Now spawn entities
CUtlVector < int > checkList ;
int i ;
int movedCount = 0 ;
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
pEntInfo = pSaveData - > GetEntityInfo ( i ) ;
pent = pEntInfo - > hEnt ;
// pSaveData->currentIndex = i;
pSaveData - > Seek ( pEntInfo - > location ) ;
// clear this out - it must be set on a per-entity basis
pSaveData - > modelSpaceOffset . Init ( ) ;
if ( pent & & ( pEntInfo - > flags & levelMask ) ) // Screen out the player if he's not to be spawned
{
if ( pEntInfo - > flags & FENTTABLE_GLOBAL )
{
DevMsg ( 2 , " Merging changes for global: %s \n " , STRING ( pEntInfo - > classname ) ) ;
// -------------------------------------------------------------------------
// Pass the "global" flag to the DLL to indicate this entity should only override
// a matching entity, not be spawned
if ( g_EntitySaveRestoreBlockHandler . RestoreGlobalEntity ( pent , pSaveData , pEntInfo ) > 0 )
{
movedCount + + ;
pEntInfo - > restoreentityindex = pEntInfo - > hEnt . Get ( ) - > entindex ( ) ;
AddRestoredEntity ( pEntInfo - > hEnt . Get ( ) ) ;
}
else
{
UTIL_RemoveImmediate ( pEntInfo - > hEnt . Get ( ) ) ;
}
// -------------------------------------------------------------------------
}
else
{
DevMsg ( 2 , " Transferring %s (%d) \n " , STRING ( pEntInfo - > classname ) , pent - > edict ( ) ? ENTINDEX ( pent - > edict ( ) ) : - 1 ) ;
CRestore restoreHelper ( pSaveData ) ;
if ( g_EntitySaveRestoreBlockHandler . RestoreEntity ( pent , & restoreHelper , pEntInfo ) < 0 )
{
UTIL_RemoveImmediate ( pent ) ;
}
else
{
// needs to be checked. Do this in a separate pass so that pointers & hierarchy can be traversed
checkList . AddToTail ( i ) ;
}
}
// Remove any entities that were removed using UTIL_Remove() as a result of the above calls to UTIL_RemoveImmediate()
gEntList . CleanupDeleteList ( ) ;
}
}
for ( i = checkList . Count ( ) - 1 ; i > = 0 ; - - i )
{
pEntInfo = pSaveData - > GetEntityInfo ( checkList [ i ] ) ;
pent = pEntInfo - > hEnt ;
// NOTE: pent can be NULL because UTIL_RemoveImmediate (called below) removes all in hierarchy
if ( ! pent )
continue ;
MDLCACHE_CRITICAL_SECTION ( ) ;
if ( ! ( pEntInfo - > flags & FENTTABLE_PLAYER ) & & UTIL_EntityInSolid ( pent ) )
{
// this can happen during normal processing - PVS is just a guess, some map areas won't exist in the new map
DevMsg ( 2 , " Suppressing %s \n " , STRING ( pEntInfo - > classname ) ) ;
UTIL_RemoveImmediate ( pent ) ;
// Remove any entities that were removed using UTIL_Remove() as a result of the above calls to UTIL_RemoveImmediate()
gEntList . CleanupDeleteList ( ) ;
}
else
{
movedCount + + ;
pEntInfo - > flags = FENTTABLE_REMOVED ;
pEntInfo - > restoreentityindex = pent - > entindex ( ) ;
AddRestoredEntity ( pent ) ;
}
}
return movedCount ;
}
# endif