engine: server: more accurate SV_ParseEdict
* Add removing trailing spaces, like GoldSrc does * Pass classname first to allow game to override it * Remove FL_CUSTOMENTITY flag usage, GoldSrc doesn't set this flag for "custom" export entities * Fix possible memory leak
This commit is contained in:
parent
2a4fafc3f9
commit
80b3f90091
1 changed files with 106 additions and 70 deletions
|
@ -1174,15 +1174,22 @@ static LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName )
|
||||||
SV_AllocPrivateData
|
SV_AllocPrivateData
|
||||||
|
|
||||||
allocate private data for a given edict
|
allocate private data for a given edict
|
||||||
|
|
||||||
|
if customentity is NULL, no "custom" entity EXPORT is being done
|
||||||
|
if customentity is not NULL, will be set to true if "custom" export
|
||||||
|
was used to create this entity
|
||||||
==============
|
==============
|
||||||
*/
|
*/
|
||||||
static edict_t* SV_AllocPrivateData( edict_t *ent, string_t className )
|
static edict_t* SV_AllocPrivateData( edict_t *ent, string_t className, qboolean *customentity )
|
||||||
{
|
{
|
||||||
const char *pszClassName;
|
const char *pszClassName;
|
||||||
LINK_ENTITY_FUNC SpawnEdict;
|
LINK_ENTITY_FUNC SpawnEdict;
|
||||||
|
|
||||||
pszClassName = STRING( className );
|
pszClassName = STRING( className );
|
||||||
|
|
||||||
|
if( customentity )
|
||||||
|
*customentity = false;
|
||||||
|
|
||||||
if( !ent )
|
if( !ent )
|
||||||
{
|
{
|
||||||
// allocate a new one
|
// allocate a new one
|
||||||
|
@ -1205,7 +1212,11 @@ static edict_t* SV_AllocPrivateData( edict_t *ent, string_t className )
|
||||||
if( svgame.physFuncs.SV_CreateEntity && svgame.physFuncs.SV_CreateEntity( ent, pszClassName ) != -1 )
|
if( svgame.physFuncs.SV_CreateEntity && svgame.physFuncs.SV_CreateEntity( ent, pszClassName ) != -1 )
|
||||||
return ent;
|
return ent;
|
||||||
|
|
||||||
|
if( customentity )
|
||||||
|
{
|
||||||
SpawnEdict = SV_GetEntityClass( "custom" );
|
SpawnEdict = SV_GetEntityClass( "custom" );
|
||||||
|
*customentity = SpawnEdict != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if( !SpawnEdict )
|
if( !SpawnEdict )
|
||||||
{
|
{
|
||||||
|
@ -1216,8 +1227,6 @@ static edict_t* SV_AllocPrivateData( edict_t *ent, string_t className )
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetBits( ent->v.flags, FL_CUSTOMENTITY ); // it's a custom entity but not a beam!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SpawnEdict( &ent->v );
|
SpawnEdict( &ent->v );
|
||||||
|
@ -1234,12 +1243,7 @@ create specified entity, alloc private data
|
||||||
*/
|
*/
|
||||||
edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className )
|
edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className )
|
||||||
{
|
{
|
||||||
edict_t *ed = SV_AllocPrivateData( ent, className );
|
return SV_AllocPrivateData( ent, className, NULL );
|
||||||
|
|
||||||
// for some reasons this flag should be immediately cleared
|
|
||||||
if( ed ) ClearBits( ed->v.flags, FL_CUSTOMENTITY );
|
|
||||||
|
|
||||||
return ed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -4820,6 +4824,15 @@ static enginefuncs_t gEngfuncs =
|
||||||
pfnPEntityOfEntIndexAllEntities,
|
pfnPEntityOfEntIndexAllEntities,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void SV_FreeKeyValueStrings( KeyValueData *kvd, int numpairs )
|
||||||
|
{
|
||||||
|
for( int i = 0; i < numpairs; i++ )
|
||||||
|
{
|
||||||
|
Mem_Free( kvd[i].szKeyName );
|
||||||
|
Mem_Free( kvd[i].szValue );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
====================
|
====================
|
||||||
SV_ParseEdict
|
SV_ParseEdict
|
||||||
|
@ -4831,36 +4844,34 @@ ed should be a properly initialized empty edict.
|
||||||
static qboolean SV_ParseEdict( char **pfile, edict_t *ent )
|
static qboolean SV_ParseEdict( char **pfile, edict_t *ent )
|
||||||
{
|
{
|
||||||
KeyValueData pkvd[256]; // per one entity
|
KeyValueData pkvd[256]; // per one entity
|
||||||
qboolean adjust_origin = false;
|
qboolean adjust_origin = false, customentity;
|
||||||
int i, numpairs = 0;
|
int i, numpairs = 0;
|
||||||
char *classname = NULL;
|
const char *classname = NULL;
|
||||||
char token[2048];
|
|
||||||
vec3_t origin;
|
|
||||||
|
|
||||||
// go through all the dictionary pairs
|
// go through all the dictionary pairs
|
||||||
while( 1 )
|
while( 1 )
|
||||||
{
|
{
|
||||||
string keyname;
|
string keyname;
|
||||||
|
char value[2048];
|
||||||
|
int len;
|
||||||
|
|
||||||
// parse key
|
// parse key
|
||||||
if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL )
|
if(( *pfile = COM_ParseFile( *pfile, keyname, sizeof( keyname ))) == NULL )
|
||||||
Host_Error( "ED_ParseEdict: EOF without closing brace\n" );
|
Host_Error( "ED_ParseEdict: EOF without closing brace\n" );
|
||||||
if( token[0] == '}' ) break; // end of desc
|
|
||||||
|
|
||||||
Q_strncpy( keyname, token, sizeof( keyname ));
|
if( keyname[0] == '}' )
|
||||||
|
break; // end of desc
|
||||||
|
|
||||||
// parse value
|
// parse value
|
||||||
if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL )
|
if(( *pfile = COM_ParseFile( *pfile, value, sizeof( value ))) == NULL )
|
||||||
Host_Error( "ED_ParseEdict: EOF without closing brace\n" );
|
Host_Error( "ED_ParseEdict: EOF without closing brace\n" );
|
||||||
|
|
||||||
if( token[0] == '}' )
|
if( value[0] == '}' )
|
||||||
Host_Error( "ED_ParseEdict: closing brace without data\n" );
|
Host_Error( "ED_ParseEdict: closing brace without data\n" );
|
||||||
|
|
||||||
// ignore attempts to set key ""
|
// ignore attempts to set empty key or value
|
||||||
if( !keyname[0] ) continue;
|
|
||||||
|
|
||||||
// "wad" field is already handled
|
// "wad" field is already handled
|
||||||
if( !Q_strcmp( keyname, "wad" ))
|
if( !keyname[0] || !value[0] || !Q_strcmp( keyname, "wad" ))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// keynames with a leading underscore are used for
|
// keynames with a leading underscore are used for
|
||||||
|
@ -4868,57 +4879,83 @@ static qboolean SV_ParseEdict( char **pfile, edict_t *ent )
|
||||||
if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && keyname[0] == '_' )
|
if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && keyname[0] == '_' )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// ignore attempts to set value ""
|
// classname must be first
|
||||||
if( !token[0] ) continue;
|
if( !Q_strcmp( keyname, "classname" ))
|
||||||
|
{
|
||||||
|
KeyValueData kvd = {
|
||||||
|
.szClassName = NULL,
|
||||||
|
.szKeyName = keyname,
|
||||||
|
.szValue = value,
|
||||||
|
.fHandled = false
|
||||||
|
};
|
||||||
|
|
||||||
|
// don't allow double classnames
|
||||||
|
if( classname != NULL )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
svgame.dllFuncs.pfnKeyValue( ent, &kvd );
|
||||||
|
|
||||||
|
// ideally, all game dlls should handle classname.
|
||||||
|
// throw an error for now, improve the logic if it causes
|
||||||
|
// compatibility issues with Xash-based games
|
||||||
|
if( !kvd.fHandled )
|
||||||
|
Host_Error( "%s: game didn't handled \"%s\" classname\n", __func__, value );
|
||||||
|
|
||||||
|
// this lets game dll override custom entity classname
|
||||||
|
// to something bogus that's exported in game dll
|
||||||
|
classname = STRING( ent->v.classname );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoldSrc removes trailing spaces
|
||||||
|
// but does this after sucking out classname
|
||||||
|
// which doesn't have similar check
|
||||||
|
for( len = Q_strlen( keyname ); len > 0 && keyname[len - 1] == ' '; len-- )
|
||||||
|
keyname[len - 1] = '\0';
|
||||||
|
|
||||||
// create keyvalue strings
|
// create keyvalue strings
|
||||||
pkvd[numpairs].szClassName = (char*)""; // unknown at this moment
|
pkvd[numpairs].szClassName = (char*)""; // unknown at this moment
|
||||||
pkvd[numpairs].szKeyName = copystring( keyname );
|
pkvd[numpairs].szKeyName = copystring( keyname );
|
||||||
pkvd[numpairs].szValue = copystring( token );
|
pkvd[numpairs].szValue = copystring( value );
|
||||||
pkvd[numpairs].fHandled = false;
|
pkvd[numpairs].fHandled = false;
|
||||||
|
numpairs++;
|
||||||
|
|
||||||
if( !Q_strcmp( keyname, "classname" ) && classname == NULL )
|
if( numpairs > ARRAYSIZE( pkvd ))
|
||||||
classname = copystring( pkvd[numpairs].szValue );
|
{
|
||||||
if( ++numpairs >= 256 ) break;
|
if( classname )
|
||||||
|
Con_Printf( S_ERROR "%s: too many keyvalue pairs for %s!\n", __func__, classname );
|
||||||
|
else Con_Printf( S_ERROR "%s: too many keyvalue pairs!\n", __func__ );
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( classname == NULL )
|
if( classname == NULL )
|
||||||
{
|
{
|
||||||
// release allocated strings
|
// release allocated strings
|
||||||
for( i = 0; i < numpairs; i++ )
|
SV_FreeKeyValueStrings( pkvd, numpairs );
|
||||||
{
|
|
||||||
Mem_Free( pkvd[i].szKeyName );
|
|
||||||
Mem_Free( pkvd[i].szValue );
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ent = SV_AllocPrivateData( ent, ALLOC_STRING( classname ));
|
ent = SV_AllocPrivateData( ent, ent->v.classname, &customentity );
|
||||||
|
|
||||||
if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME ))
|
if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME ))
|
||||||
{
|
{
|
||||||
// release allocated strings
|
// release allocated strings
|
||||||
for( i = 0; i < numpairs; i++ )
|
SV_FreeKeyValueStrings( pkvd, numpairs );
|
||||||
{
|
|
||||||
Mem_Free( pkvd[i].szKeyName );
|
|
||||||
Mem_Free( pkvd[i].szValue );
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( FBitSet( ent->v.flags, FL_CUSTOMENTITY ))
|
if( customentity )
|
||||||
{
|
{
|
||||||
if( numpairs < 256 )
|
KeyValueData kvd = {
|
||||||
{
|
.szClassName = (char *)"custom",
|
||||||
pkvd[numpairs].szClassName = (char*)"custom";
|
.szKeyName = (char *)"customclass",
|
||||||
pkvd[numpairs].szKeyName = (char*)"customclass";
|
.szValue = (char *)classname,
|
||||||
pkvd[numpairs].szValue = classname;
|
.fHandled = false
|
||||||
pkvd[numpairs].fHandled = false;
|
};
|
||||||
numpairs++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear it now - no longer used
|
svgame.dllFuncs.pfnKeyValue( ent, &kvd );
|
||||||
ClearBits( ent->v.flags, FL_CUSTOMENTITY );
|
// no fHandled check, GoldSrc behavior
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HACKS_RELATED_HLMODS
|
#ifdef HACKS_RELATED_HLMODS
|
||||||
|
@ -4932,6 +4969,14 @@ static qboolean SV_ParseEdict( char **pfile, edict_t *ent )
|
||||||
|
|
||||||
for( i = 0; i < numpairs; i++ )
|
for( i = 0; i < numpairs; i++ )
|
||||||
{
|
{
|
||||||
|
char *keyname, *value;
|
||||||
|
char temp[MAX_VA_STRING];
|
||||||
|
|
||||||
|
#if 0 // this is stupid bug in GoldSrc, disable
|
||||||
|
if( !Q_strcmp( pkvd[i].szValue, classname ))
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
|
|
||||||
if( !Q_strcmp( pkvd[i].szKeyName, "angle" ))
|
if( !Q_strcmp( pkvd[i].szKeyName, "angle" ))
|
||||||
{
|
{
|
||||||
float flYawAngle = Q_atof( pkvd[i].szValue );
|
float flYawAngle = Q_atof( pkvd[i].szValue );
|
||||||
|
@ -4942,8 +4987,6 @@ static qboolean SV_ParseEdict( char **pfile, edict_t *ent )
|
||||||
|
|
||||||
if( flYawAngle >= 0.0f )
|
if( flYawAngle >= 0.0f )
|
||||||
{
|
{
|
||||||
char temp[MAX_VA_STRING];
|
|
||||||
|
|
||||||
Q_snprintf( temp, sizeof( temp ), "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] );
|
Q_snprintf( temp, sizeof( temp ), "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] );
|
||||||
pkvd[i].szValue = copystring( temp );
|
pkvd[i].szValue = copystring( temp );
|
||||||
}
|
}
|
||||||
|
@ -4954,11 +4997,10 @@ static qboolean SV_ParseEdict( char **pfile, edict_t *ent )
|
||||||
else pkvd[i].szValue = copystring( "0 0 0" ); // technically an error
|
else pkvd[i].szValue = copystring( "0 0 0" ); // technically an error
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HACKS_RELATED_HLMODS
|
|
||||||
if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" ))
|
if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" ))
|
||||||
{
|
{
|
||||||
char temp[MAX_VA_STRING];
|
|
||||||
char *pstart = pkvd[i].szValue;
|
char *pstart = pkvd[i].szValue;
|
||||||
|
vec3_t origin;
|
||||||
|
|
||||||
COM_ParseVector( &pstart, origin, 3 );
|
COM_ParseVector( &pstart, origin, 3 );
|
||||||
Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these
|
Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these
|
||||||
|
@ -4966,24 +5008,18 @@ static qboolean SV_ParseEdict( char **pfile, edict_t *ent )
|
||||||
Q_snprintf( temp, sizeof( temp ), "%g %g %g", origin[0], origin[1], origin[2] - 16.0f );
|
Q_snprintf( temp, sizeof( temp ), "%g %g %g", origin[0], origin[1], origin[2] - 16.0f );
|
||||||
pkvd[i].szValue = copystring( temp );
|
pkvd[i].szValue = copystring( temp );
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if( !pkvd[i].fHandled )
|
// do not leak memory if game overwritten these pointers
|
||||||
{
|
keyname = pkvd[i].szKeyName;
|
||||||
pkvd[i].szClassName = classname;
|
value = pkvd[i].szValue;
|
||||||
|
|
||||||
|
pkvd[i].szClassName = (char *)classname;
|
||||||
svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] );
|
svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] );
|
||||||
|
|
||||||
|
Mem_Free( keyname );
|
||||||
|
Mem_Free( value );
|
||||||
}
|
}
|
||||||
|
|
||||||
// no reason to keep this data
|
|
||||||
if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szKeyName ))
|
|
||||||
Mem_Free( pkvd[i].szKeyName );
|
|
||||||
|
|
||||||
if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szValue ))
|
|
||||||
Mem_Free( pkvd[i].szValue );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( Mem_IsAllocatedExt( host.mempool, classname ))
|
|
||||||
Mem_Free( classname );
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue