From a0c29e7dd67abb15c74c85f07741784877edfdcd Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Mon, 2 Sep 2013 11:39:10 -0700 Subject: General: * Fixed a variety of server browser issues with mods based on this SDK * Fixed many warnings on various platforms * Added source code for fgdlib and raytrace * Updated many source files with the latest shared source from TF2. OSX: * Added support for Xcode 4.6 * Switched OSX builds to use Xcode instead of makefiles * Moved libs from src/lib/osx32 to src/lib/public/osx32 or src/lib/common/osx32 to match windows better. Linux: * Moved libs from src/lib/linux32 to src/lib/public/linux32 or src/lib/common/linux32 to match windows better. --- mp/src/fgdlib/gamedata.cpp | 886 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 886 insertions(+) create mode 100644 mp/src/fgdlib/gamedata.cpp (limited to 'mp/src/fgdlib/gamedata.cpp') diff --git a/mp/src/fgdlib/gamedata.cpp b/mp/src/fgdlib/gamedata.cpp new file mode 100644 index 00000000..cd94b23d --- /dev/null +++ b/mp/src/fgdlib/gamedata.cpp @@ -0,0 +1,886 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//============================================================================= + +#include +#include +#include +#include +#include "fgdlib/GameData.h" +#include "fgdlib/HelperInfo.h" +#include "KeyValues.h" +#include "filesystem_tools.h" +#include "tier1/strtools.h" +#include "utlmap.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#pragma warning(disable:4244) + + +const int MAX_ERRORS = 5; + + +static GameDataMessageFunc_t g_pMsgFunc = NULL; + + +//----------------------------------------------------------------------------- +// Sets the function used for emitting error messages while loading gamedata files. +//----------------------------------------------------------------------------- +void GDSetMessageFunc(GameDataMessageFunc_t pFunc) +{ + g_pMsgFunc = pFunc; +} + + +//----------------------------------------------------------------------------- +// Purpose: Fetches the next token from the file. +// Input : tr - +// ppszStore - Destination buffer, one of the following: +// pointer to NULL - token will be placed in an allocated buffer +// pointer to non-NULL buffer - token will be placed in buffer +// ttexpecting - +// pszExpecting - +// Output : +//----------------------------------------------------------------------------- +static bool DoGetToken(TokenReader &tr, char **ppszStore, int nSize, trtoken_t ttexpecting, const char *pszExpecting) +{ + trtoken_t ttype; + + if (*ppszStore != NULL) + { + // Reads the token into the given buffer. + ttype = tr.NextToken(*ppszStore, nSize); + } + else + { + // Allocates a buffer to hold the token. + ttype = tr.NextTokenDynamic(ppszStore); + } + + if (ttype == TOKENSTRINGTOOLONG) + { + GDError(tr, "unterminated string or string too long"); + return false; + } + + // + // Check for a bad token type. + // + char *pszStore = *ppszStore; + bool bBadTokenType = false; + if ((ttype != ttexpecting) && (ttexpecting != TOKENNONE)) + { + // + // If we were expecting a string and got an integer, don't worry about it. + // We can translate from integer to string. + // + if (!((ttexpecting == STRING) && (ttype == INTEGER))) + { + bBadTokenType = true; + } + } + + if (bBadTokenType && (pszExpecting == NULL)) + { + // + // We didn't get the expected token type but no expected + // string was specified. + // + char *pszTokenName; + switch (ttexpecting) + { + case IDENT: + { + pszTokenName = "identifier"; + break; + } + + case INTEGER: + { + pszTokenName = "integer"; + break; + } + + case STRING: + { + pszTokenName = "string"; + break; + } + + case OPERATOR: + default: + { + pszTokenName = "symbol"; + break; + } + } + + GDError(tr, "expecting %s", pszTokenName); + return false; + } + else if (bBadTokenType || ((pszExpecting != NULL) && !IsToken(pszStore, pszExpecting))) + { + // + // An expected string was specified, and we got either the wrong type or + // the right type but the wrong string, + // + GDError(tr, "expecting '%s', but found '%s'", pszExpecting, pszStore); + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : tr - +// error - +// Output : +//----------------------------------------------------------------------------- +bool GDError(TokenReader &tr, const char *error, ...) +{ + char szBuf[128]; + va_list vl; + va_start(vl, error); + vsprintf(szBuf, error, vl); + va_end(vl); + + if (g_pMsgFunc) + { + // HACK: should use an enumeration for error level + g_pMsgFunc(1, tr.Error(szBuf)); + } + + if (tr.GetErrorCount() >= MAX_ERRORS) + { + if (g_pMsgFunc) + { + // HACK: should use an enumeration for error level + g_pMsgFunc(1, " - too many errors; aborting."); + } + + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Fetches the next token from the file. +// Input : tr - The token reader object with which to fetch the token. +// pszStore - Buffer in which to place the token, NULL to discard the token. +// ttexpecting - The token type that we are expecting. If this is not TOKENNONE +// and token type read is different, the operation will fail. +// pszExpecting - The token string that we are expecting. If this string +// is not NULL and the token string read is different, the operation will fail. +// Output : Returns TRUE if the operation succeeded, FALSE if there was an error. +// If there was an error, the error will be reported in the message window. +//----------------------------------------------------------------------------- +bool GDGetToken(TokenReader &tr, char *pszStore, int nSize, trtoken_t ttexpecting, const char *pszExpecting) +{ + Assert(pszStore != NULL); + if (pszStore != NULL) + { + return DoGetToken(tr, &pszStore, nSize, ttexpecting, pszExpecting); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Fetches the next token from the file. +// Input : tr - The token reader object with which to fetch the token. +// pszStore - Buffer in which to place the token, NULL to discard the token. +// ttexpecting - The token type that we are expecting. If this is not TOKENNONE +// and token type read is different, the operation will fail. +// pszExpecting - The token string that we are expecting. If this string +// is not NULL and the token string read is different, the operation will fail. +// Output : Returns TRUE if the operation succeeded, FALSE if there was an error. +// If there was an error, the error will be reported in the message window. +//----------------------------------------------------------------------------- +bool GDSkipToken(TokenReader &tr, trtoken_t ttexpecting, const char *pszExpecting) +{ + // + // Read the next token into a buffer and discard it. + // + char szDiscardBuf[MAX_TOKEN]; + char *pszDiscardBuf = szDiscardBuf; + return DoGetToken(tr, &pszDiscardBuf, sizeof(szDiscardBuf), ttexpecting, pszExpecting); +} + + +//----------------------------------------------------------------------------- +// Purpose: Fetches the next token from the file, allocating a buffer exactly +// large enough to hold the token. +// Input : tr - +// ppszStore - +// ttexpecting - +// pszExpecting - +// Output : +//----------------------------------------------------------------------------- +bool GDGetTokenDynamic(TokenReader &tr, char **ppszStore, trtoken_t ttexpecting, const char *pszExpecting) +{ + if (ppszStore == NULL) + { + return false; + } + + *ppszStore = NULL; + return DoGetToken(tr, ppszStore, -1, ttexpecting, pszExpecting); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +GameData::GameData(void) +{ + m_nMaxMapCoord = 8192; + m_nMinMapCoord = -8192; + m_InstanceClass = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor. +//----------------------------------------------------------------------------- +GameData::~GameData(void) +{ + ClearData(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void GameData::ClearData(void) +{ + // delete classes. + int nCount = m_Classes.Count(); + for (int i = 0; i < nCount; i++) + { + GDclass *pm = m_Classes.Element(i); + delete pm; + } + m_Classes.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads a gamedata (FGD) file into this object. +// Input : pszFilename - +// Output : Returns TRUE on success, FALSE on failure. +//----------------------------------------------------------------------------- +BOOL GameData::Load(const char *pszFilename) +{ + TokenReader tr; + + if(GetFileAttributes(pszFilename) == 0xffffffff) + return FALSE; + + if(!tr.Open(pszFilename)) + return FALSE; + + trtoken_t ttype; + char szToken[128]; + + while (1) + { + if (tr.GetErrorCount() >= MAX_ERRORS) + { + break; + } + + ttype = tr.NextToken(szToken, sizeof(szToken)); + + if(ttype == TOKENEOF) + break; + + if(ttype != OPERATOR || !IsToken(szToken, "@")) + { + if(!GDError(tr, "expected @")) + return FALSE; + } + + // check what kind it is, and parse a new object + if (tr.NextToken(szToken, sizeof(szToken)) != IDENT) + { + if(!GDError(tr, "expected identifier after @")) + return FALSE; + } + + if (IsToken(szToken, "baseclass") || IsToken(szToken, "pointclass") || IsToken(szToken, "solidclass") || IsToken(szToken, "keyframeclass") || + IsToken(szToken, "moveclass") || IsToken(szToken, "npcclass") || IsToken(szToken, "filterclass")) + { + // + // New class. + // + GDclass *pNewClass = new GDclass; + if (!pNewClass->InitFromTokens(tr, this)) + { + tr.IgnoreTill(OPERATOR, "@"); // go to next section + delete pNewClass; + } + else + { + if (IsToken(szToken, "baseclass")) // Not directly available to user. + { + pNewClass->SetBaseClass(true); + } + else if (IsToken(szToken, "pointclass")) // Generic point class. + { + pNewClass->SetPointClass(true); + } + else if (IsToken(szToken, "solidclass")) // Tied to solids. + { + pNewClass->SetSolidClass(true); + } + else if (IsToken(szToken, "npcclass")) // NPC class - can be spawned by npc_maker. + { + pNewClass->SetPointClass(true); + pNewClass->SetNPCClass(true); + } + else if (IsToken(szToken, "filterclass")) // Filter class - can be used as a filter + { + pNewClass->SetPointClass(true); + pNewClass->SetFilterClass(true); + } + else if (IsToken(szToken, "moveclass")) // Animating + { + pNewClass->SetMoveClass(true); + pNewClass->SetPointClass(true); + } + else if (IsToken(szToken, "keyframeclass")) // Animation keyframes + { + pNewClass->SetKeyFrameClass(true); + pNewClass->SetPointClass(true); + } + + // Check and see if this new class matches an existing one. If so we will override the previous definition. + int nExistingClassIndex = 0; + GDclass *pExistingClass = ClassForName(pNewClass->GetName(), &nExistingClassIndex); + if (NULL != pExistingClass) + { + m_Classes.InsertAfter(nExistingClassIndex, pNewClass); + m_Classes.Remove(nExistingClassIndex); + } + else + { + m_Classes.AddToTail(pNewClass); + } + } + } + else if (IsToken(szToken, "include")) + { + if (GDGetToken(tr, szToken, sizeof(szToken), STRING)) + { + // Let's assume it's in the same directory. + char justPath[MAX_PATH], loadFilename[MAX_PATH]; + if ( Q_ExtractFilePath( pszFilename, justPath, sizeof( justPath ) ) ) + { + Q_snprintf( loadFilename, sizeof( loadFilename ), "%s%s", justPath, szToken ); + } + else + { + Q_strncpy( loadFilename, szToken, sizeof( loadFilename ) ); + } + + // First try our fully specified directory + if (!Load(loadFilename)) + { + // Failing that, try our start directory + if (!Load(szToken)) + { + GDError(tr, "error including file: %s", szToken); + } + } + } + } + else if (IsToken(szToken, "mapsize")) + { + if (!ParseMapSize(tr)) + { + // Error in map size specifier, skip to next @ sign. + tr.IgnoreTill(OPERATOR, "@"); + } + } + else if ( IsToken( szToken, "materialexclusion" ) ) + { + if ( !LoadFGDMaterialExclusions( tr ) ) + { + // FGD exclusions not defined; skip to next @ sign. + tr.IgnoreTill(OPERATOR, "@"); + } + } + else if ( IsToken( szToken, "autovisgroup" ) ) + { + if ( !LoadFGDAutoVisGroups( tr ) ) + { + // FGD AutoVisGroups not defined; skip to next @ sign. + tr.IgnoreTill(OPERATOR, "@"); + } + } + else + { + GDError(tr, "unrecognized section name %s", szToken); + tr.IgnoreTill(OPERATOR, "@"); + } + } + + if (tr.GetErrorCount() > 0) + { + return FALSE; + } + + tr.Close(); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// Purpose: Parses the "mapsize" specifier, which should be of the form: +// +// mapsize(min, max) +// +// ex: mapsize(-8192, 8192) +// +// Input : tr - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool GameData::ParseMapSize(TokenReader &tr) +{ + if (!GDSkipToken(tr, OPERATOR, "(")) + { + return false; + } + + char szToken[128]; + if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER)) + { + return false; + } + int nMin = atoi(szToken); + + if (!GDSkipToken(tr, OPERATOR, ",")) + { + return false; + } + + if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER)) + { + return false; + } + int nMax = atoi(szToken); + + if (nMin != nMax) + { + m_nMinMapCoord = min(nMin, nMax); + m_nMaxMapCoord = max(nMin, nMax); + } + + if (!GDSkipToken(tr, OPERATOR, ")")) + { + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszName - +// piIndex - +// Output : +//----------------------------------------------------------------------------- +GDclass *GameData::ClassForName(const char *pszName, int *piIndex) +{ + int nCount = m_Classes.Count(); + for (int i = 0; i < nCount; i++) + { + GDclass *mp = m_Classes.Element(i); + if(!strcmp(mp->GetName(), pszName)) + { + if(piIndex) + piIndex[0] = i; + return mp; + } + } + + return NULL; +} + + +// These are 'standard' keys that every entity uses, but they aren't specified that way in the .fgd +static const char *RequiredKeys[] = +{ + "Origin", + "Angles", + NULL +}; + +//----------------------------------------------------------------------------- +// Purpose: this function will set up the initial class about to be instanced +// Input : pszClassName - the class name of the entity to be instanced +// pszInstancePrefix - the prefix to be used for all name fields +// Origin - the origin offset of the instance +// Angles - the angle rotation of the instance +// Output : if successful, will return the game data class of the class name +//----------------------------------------------------------------------------- +GDclass *GameData::BeginInstanceRemap( const char *pszClassName, const char *pszInstancePrefix, Vector &Origin, QAngle &Angle ) +{ + m_InstanceOrigin = Origin; + m_InstanceAngle = Angle; + AngleMatrix( m_InstanceAngle, m_InstanceOrigin, m_InstanceMat ); + + strcpy( m_InstancePrefix, pszInstancePrefix ); + + if ( m_InstanceClass ) + { + delete m_InstanceClass; + m_InstanceClass = NULL; + } + + if ( strcmpi( pszClassName, "info_overlay_accessor" ) == 0 ) + { // yucky hack for a made up entity in the bsp process + pszClassName = "info_overlay"; + } + + GDclass *BaseClass = ClassForName( pszClassName ); + if ( BaseClass ) + { + m_InstanceClass = new GDclass(); + m_InstanceClass->Parent = this; + m_InstanceClass->AddBase( BaseClass ); + + for( int i = 0; RequiredKeys[ i ]; i++ ) + { + if ( m_InstanceClass->VarForName( RequiredKeys[ i ] ) == NULL ) + { + BaseClass = ClassForName( RequiredKeys[ i ] ); + if ( BaseClass ) + { + m_InstanceClass->AddBase( BaseClass ); + } + } + } + } + else + { + m_InstanceClass = NULL; + } + + return m_InstanceClass; +} + + +enum tRemapOperation +{ + REMAP_NAME = 0, + REMAP_POSITION, + REMAP_ANGLE, + REMAP_ANGLE_NEGATIVE_PITCH, +}; + + +static CUtlMap< GDIV_TYPE, tRemapOperation > RemapOperation; + + +//----------------------------------------------------------------------------- +// Purpose: function to sort the class type for the RemapOperations map +// Input : type1 - the first type to compare against +// type2 - the second type to compare against +// Output : returns true if the first type is less than the second one +//----------------------------------------------------------------------------- +static bool CUtlType_LessThan( const GDIV_TYPE &type1, const GDIV_TYPE &type2 ) +{ + return ( type1 < type2 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will attempt to remap a key's value +// Input : pszKey - the name of the key +// pszInvalue - the original value +// AllowNameRemapping - only do name remapping if this parameter is true. +// this is generally only false on the instance level. +// Output : returns true if the value changed +// pszOutValue - the new value if changed +//----------------------------------------------------------------------------- +bool GameData::RemapKeyValue( const char *pszKey, const char *pszInValue, char *pszOutValue, TNameFixup NameFixup ) +{ + if ( RemapOperation.Count() == 0 ) + { + RemapOperation.SetLessFunc( &CUtlType_LessThan ); + RemapOperation.Insert( ivAngle, REMAP_ANGLE ); + RemapOperation.Insert( ivTargetDest, REMAP_NAME ); + RemapOperation.Insert( ivTargetSrc, REMAP_NAME ); + RemapOperation.Insert( ivOrigin, REMAP_POSITION ); + RemapOperation.Insert( ivAxis, REMAP_ANGLE ); + RemapOperation.Insert( ivAngleNegativePitch, REMAP_ANGLE_NEGATIVE_PITCH ); + } + + if ( !m_InstanceClass ) + { + return false; + } + + GDinputvariable *KVVar = m_InstanceClass->VarForName( pszKey ); + if ( !KVVar ) + { + return false; + } + + GDIV_TYPE KVType = KVVar->GetType(); + int KVRemapIndex = RemapOperation.Find( KVType ); + if ( KVRemapIndex == RemapOperation.InvalidIndex() ) + { + return false; + } + + strcpy( pszOutValue, pszInValue ); + + switch( RemapOperation[ KVRemapIndex ] ) + { + case REMAP_NAME: + if ( KVType != ivInstanceVariable ) + { + RemapNameField( pszInValue, pszOutValue, NameFixup ); + } + break; + + case REMAP_POSITION: + { + Vector inPoint( 0.0f, 0.0f, 0.0f ), outPoint; + + sscanf ( pszInValue, "%f %f %f", &inPoint.x, &inPoint.y, &inPoint.z ); + VectorTransform( inPoint, m_InstanceMat, outPoint ); + sprintf( pszOutValue, "%g %g %g", outPoint.x, outPoint.y, outPoint.z ); + } + break; + + case REMAP_ANGLE: + if ( m_InstanceAngle.x != 0.0f || m_InstanceAngle.y != 0.0f || m_InstanceAngle.z != 0.0f ) + { + QAngle inAngles( 0.0f, 0.0f, 0.0f ), outAngles; + matrix3x4_t angToWorld, localMatrix; + + sscanf ( pszInValue, "%f %f %f", &inAngles.x, &inAngles.y, &inAngles.z ); + + AngleMatrix( inAngles, angToWorld ); + MatrixMultiply( m_InstanceMat, angToWorld, localMatrix ); + MatrixAngles( localMatrix, outAngles ); + + sprintf( pszOutValue, "%g %g %g", outAngles.x, outAngles.y, outAngles.z ); + } + break; + + case REMAP_ANGLE_NEGATIVE_PITCH: + if ( m_InstanceAngle.x != 0.0f || m_InstanceAngle.y != 0.0f || m_InstanceAngle.z != 0.0f ) + { + QAngle inAngles( 0.0f, 0.0f, 0.0f ), outAngles; + matrix3x4_t angToWorld, localMatrix; + + sscanf ( pszInValue, "%f", &inAngles.x ); // just the pitch + inAngles.x = -inAngles.x; + + AngleMatrix( inAngles, angToWorld ); + MatrixMultiply( m_InstanceMat, angToWorld, localMatrix ); + MatrixAngles( localMatrix, outAngles ); + + sprintf( pszOutValue, "%g", -outAngles.x ); // just the pitch + } + break; + } + + return ( strcmpi( pszInValue, pszOutValue ) != 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will attempt to remap a name field. +// Input : pszInvalue - the original value +// AllowNameRemapping - only do name remapping if this parameter is true. +// this is generally only false on the instance level. +// Output : returns true if the value changed +// pszOutValue - the new value if changed +//----------------------------------------------------------------------------- +bool GameData::RemapNameField( const char *pszInValue, char *pszOutValue, TNameFixup NameFixup ) +{ + strcpy( pszOutValue, pszInValue ); + + if ( pszInValue[ 0 ] && pszInValue[ 0 ] != '@' ) + { // ! at the start of a value means it is global and should not be remaped + switch( NameFixup ) + { + case NAME_FIXUP_PREFIX: + sprintf( pszOutValue, "%s-%s", m_InstancePrefix, pszInValue ); + break; + + case NAME_FIXUP_POSTFIX: + sprintf( pszOutValue, "%s-%s", pszInValue, m_InstancePrefix ); + break; + } + } + + return ( strcmpi( pszInValue, pszOutValue ) != 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gathers any FGD-defined material directory exclusions +// Input : +// Output : +//----------------------------------------------------------------------------- +bool GameData::LoadFGDMaterialExclusions( TokenReader &tr ) +{ + if ( !GDSkipToken( tr, OPERATOR, "[" ) ) + { + return false; + } + while ( 1 ) + { + char szToken[128]; + bool bMatchFound = false; + + if ( tr.PeekTokenType( szToken, sizeof( szToken ) ) == OPERATOR ) + { + break; + } + else if ( GDGetToken( tr, szToken, sizeof( szToken ), STRING ) ) + { + // Make sure we haven't loaded this from another FGD + for ( int i = 0; i < m_FGDMaterialExclusions.Count(); i++ ) + { + if ( !stricmp( szToken, m_FGDMaterialExclusions[i].szDirectory ) ) + { + bMatchFound = true; + break; + } + } + + // Parse the string + if ( bMatchFound == false ) + { + int index = m_FGDMaterialExclusions.AddToTail(); + Q_strncpy( m_FGDMaterialExclusions[index].szDirectory, szToken, sizeof( m_FGDMaterialExclusions[index].szDirectory ) ); + m_FGDMaterialExclusions[index].bUserGenerated = false; + } + } + } + + // + // Closing square brace. + // + if ( !GDSkipToken( tr, OPERATOR, "]" ) ) + { + return( FALSE ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Gathers any FGD-defined Auto VisGroups +// Input : +// Output : +//----------------------------------------------------------------------------- +bool GameData::LoadFGDAutoVisGroups( TokenReader &tr ) +{ + int gindex = 0; // Index of AutoVisGroups + int cindex = 0; // Index of Classes + + char szToken[128]; + + // Handle the Parent -- World Geometry, Entities, World Detail + if ( GDSkipToken( tr, OPERATOR, "=" ) ) + { + // We expect a name + if ( !GDGetToken( tr, szToken, sizeof( szToken ), STRING ) ) + { + return( FALSE ); + } + + gindex = m_FGDAutoVisGroups.AddToTail(); + Q_strncpy( m_FGDAutoVisGroups[gindex].szParent, szToken, sizeof( m_FGDAutoVisGroups[gindex].szParent ) ); + + // We expect a Class + if ( !GDSkipToken( tr, OPERATOR, "[" ) ) + { + return( FALSE ); + } + } + + // Handle the Class(es) -- Brush Entities, Occluders, Lights + while ( 1 ) + { + if ( GDGetToken( tr, szToken, sizeof( szToken ), STRING ) ) + { + cindex = m_FGDAutoVisGroups[gindex].m_Classes.AddToTail(); + Q_strncpy( m_FGDAutoVisGroups[gindex].m_Classes[cindex].szClass, szToken, sizeof( m_FGDAutoVisGroups[gindex].m_Classes[cindex].szClass ) ); + + if ( !GDSkipToken( tr, OPERATOR, "[" ) ) + { + return( FALSE ); + } + + // Parse objects/entities -- func_detail, point_template, light_spot + while ( 1 ) + { + if ( tr.PeekTokenType( szToken, sizeof( szToken ) ) == OPERATOR ) + { + break; + } + + if ( !GDGetToken( tr, szToken, sizeof( szToken ), STRING ) ) + { + return( FALSE ); + } + + m_FGDAutoVisGroups[gindex].m_Classes[cindex].szEntities.CopyAndAddToTail( szToken ); + + } + + if ( !GDSkipToken( tr, OPERATOR, "]" ) ) + { + return( FALSE ); + } + + // See if we have another Class coming up + if ( tr.PeekTokenType( szToken, sizeof( szToken ) ) == STRING ) + { + continue; + } + + // If no more Classes, we now expect a terminating ']' + if ( !GDSkipToken( tr, OPERATOR, "]" ) ) + { + return( FALSE ); + } + + // We're done + return true; + } + // We don't have another Class; look for a terminating brace + else + { + if ( !GDSkipToken( tr, OPERATOR, "]" ) ) + { + return( FALSE ); + } + } + } + + // Safety net + GDError( tr, "Malformed AutoVisGroup -- Last processed: %s", szToken ); + return( FALSE ); +} + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgoff.h" -- cgit v1.2.3