Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

886 lines
23 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=============================================================================
  4. #include <windows.h>
  5. #include <tier0/dbg.h>
  6. #include <io.h>
  7. #include <WorldSize.h>
  8. #include "fgdlib/GameData.h"
  9. #include "fgdlib/HelperInfo.h"
  10. #include "KeyValues.h"
  11. #include "filesystem_tools.h"
  12. #include "tier1/strtools.h"
  13. #include "utlmap.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. #pragma warning(disable:4244)
  17. const int MAX_ERRORS = 5;
  18. static GameDataMessageFunc_t g_pMsgFunc = NULL;
  19. //-----------------------------------------------------------------------------
  20. // Sets the function used for emitting error messages while loading gamedata files.
  21. //-----------------------------------------------------------------------------
  22. void GDSetMessageFunc(GameDataMessageFunc_t pFunc)
  23. {
  24. g_pMsgFunc = pFunc;
  25. }
  26. //-----------------------------------------------------------------------------
  27. // Purpose: Fetches the next token from the file.
  28. // Input : tr -
  29. // ppszStore - Destination buffer, one of the following:
  30. // pointer to NULL - token will be placed in an allocated buffer
  31. // pointer to non-NULL buffer - token will be placed in buffer
  32. // ttexpecting -
  33. // pszExpecting -
  34. // Output :
  35. //-----------------------------------------------------------------------------
  36. static bool DoGetToken(TokenReader &tr, char **ppszStore, int nSize, trtoken_t ttexpecting, const char *pszExpecting)
  37. {
  38. trtoken_t ttype;
  39. if (*ppszStore != NULL)
  40. {
  41. // Reads the token into the given buffer.
  42. ttype = tr.NextToken(*ppszStore, nSize);
  43. }
  44. else
  45. {
  46. // Allocates a buffer to hold the token.
  47. ttype = tr.NextTokenDynamic(ppszStore);
  48. }
  49. if (ttype == TOKENSTRINGTOOLONG)
  50. {
  51. GDError(tr, "unterminated string or string too long");
  52. return false;
  53. }
  54. //
  55. // Check for a bad token type.
  56. //
  57. char *pszStore = *ppszStore;
  58. bool bBadTokenType = false;
  59. if ((ttype != ttexpecting) && (ttexpecting != TOKENNONE))
  60. {
  61. //
  62. // If we were expecting a string and got an integer, don't worry about it.
  63. // We can translate from integer to string.
  64. //
  65. if (!((ttexpecting == STRING) && (ttype == INTEGER)))
  66. {
  67. bBadTokenType = true;
  68. }
  69. }
  70. if (bBadTokenType && (pszExpecting == NULL))
  71. {
  72. //
  73. // We didn't get the expected token type but no expected
  74. // string was specified.
  75. //
  76. char *pszTokenName;
  77. switch (ttexpecting)
  78. {
  79. case IDENT:
  80. {
  81. pszTokenName = "identifier";
  82. break;
  83. }
  84. case INTEGER:
  85. {
  86. pszTokenName = "integer";
  87. break;
  88. }
  89. case STRING:
  90. {
  91. pszTokenName = "string";
  92. break;
  93. }
  94. case OPERATOR:
  95. default:
  96. {
  97. pszTokenName = "symbol";
  98. break;
  99. }
  100. }
  101. GDError(tr, "expecting %s", pszTokenName);
  102. return false;
  103. }
  104. else if (bBadTokenType || ((pszExpecting != NULL) && !IsToken(pszStore, pszExpecting)))
  105. {
  106. //
  107. // An expected string was specified, and we got either the wrong type or
  108. // the right type but the wrong string,
  109. //
  110. GDError(tr, "expecting '%s', but found '%s'", pszExpecting, pszStore);
  111. return false;
  112. }
  113. return true;
  114. }
  115. //-----------------------------------------------------------------------------
  116. // Purpose:
  117. // Input : tr -
  118. // error -
  119. // Output :
  120. //-----------------------------------------------------------------------------
  121. bool GDError(TokenReader &tr, const char *error, ...)
  122. {
  123. char szBuf[128];
  124. va_list vl;
  125. va_start(vl, error);
  126. vsprintf(szBuf, error, vl);
  127. va_end(vl);
  128. if (g_pMsgFunc)
  129. {
  130. // HACK: should use an enumeration for error level
  131. g_pMsgFunc(1, tr.Error(szBuf));
  132. }
  133. if (tr.GetErrorCount() >= MAX_ERRORS)
  134. {
  135. if (g_pMsgFunc)
  136. {
  137. // HACK: should use an enumeration for error level
  138. g_pMsgFunc(1, " - too many errors; aborting.");
  139. }
  140. return false;
  141. }
  142. return true;
  143. }
  144. //-----------------------------------------------------------------------------
  145. // Purpose: Fetches the next token from the file.
  146. // Input : tr - The token reader object with which to fetch the token.
  147. // pszStore - Buffer in which to place the token, NULL to discard the token.
  148. // ttexpecting - The token type that we are expecting. If this is not TOKENNONE
  149. // and token type read is different, the operation will fail.
  150. // pszExpecting - The token string that we are expecting. If this string
  151. // is not NULL and the token string read is different, the operation will fail.
  152. // Output : Returns TRUE if the operation succeeded, FALSE if there was an error.
  153. // If there was an error, the error will be reported in the message window.
  154. //-----------------------------------------------------------------------------
  155. bool GDGetToken(TokenReader &tr, char *pszStore, int nSize, trtoken_t ttexpecting, const char *pszExpecting)
  156. {
  157. Assert(pszStore != NULL);
  158. if (pszStore != NULL)
  159. {
  160. return DoGetToken(tr, &pszStore, nSize, ttexpecting, pszExpecting);
  161. }
  162. return false;
  163. }
  164. //-----------------------------------------------------------------------------
  165. // Purpose: Fetches the next token from the file.
  166. // Input : tr - The token reader object with which to fetch the token.
  167. // pszStore - Buffer in which to place the token, NULL to discard the token.
  168. // ttexpecting - The token type that we are expecting. If this is not TOKENNONE
  169. // and token type read is different, the operation will fail.
  170. // pszExpecting - The token string that we are expecting. If this string
  171. // is not NULL and the token string read is different, the operation will fail.
  172. // Output : Returns TRUE if the operation succeeded, FALSE if there was an error.
  173. // If there was an error, the error will be reported in the message window.
  174. //-----------------------------------------------------------------------------
  175. bool GDSkipToken(TokenReader &tr, trtoken_t ttexpecting, const char *pszExpecting)
  176. {
  177. //
  178. // Read the next token into a buffer and discard it.
  179. //
  180. char szDiscardBuf[MAX_TOKEN];
  181. char *pszDiscardBuf = szDiscardBuf;
  182. return DoGetToken(tr, &pszDiscardBuf, sizeof(szDiscardBuf), ttexpecting, pszExpecting);
  183. }
  184. //-----------------------------------------------------------------------------
  185. // Purpose: Fetches the next token from the file, allocating a buffer exactly
  186. // large enough to hold the token.
  187. // Input : tr -
  188. // ppszStore -
  189. // ttexpecting -
  190. // pszExpecting -
  191. // Output :
  192. //-----------------------------------------------------------------------------
  193. bool GDGetTokenDynamic(TokenReader &tr, char **ppszStore, trtoken_t ttexpecting, const char *pszExpecting)
  194. {
  195. if (ppszStore == NULL)
  196. {
  197. return false;
  198. }
  199. *ppszStore = NULL;
  200. return DoGetToken(tr, ppszStore, -1, ttexpecting, pszExpecting);
  201. }
  202. //-----------------------------------------------------------------------------
  203. // Purpose: Constructor.
  204. //-----------------------------------------------------------------------------
  205. GameData::GameData(void)
  206. {
  207. m_nMaxMapCoord = 8192;
  208. m_nMinMapCoord = -8192;
  209. m_InstanceClass = NULL;
  210. }
  211. //-----------------------------------------------------------------------------
  212. // Purpose: Destructor.
  213. //-----------------------------------------------------------------------------
  214. GameData::~GameData(void)
  215. {
  216. ClearData();
  217. }
  218. //-----------------------------------------------------------------------------
  219. // Purpose:
  220. //-----------------------------------------------------------------------------
  221. void GameData::ClearData(void)
  222. {
  223. // delete classes.
  224. int nCount = m_Classes.Count();
  225. for (int i = 0; i < nCount; i++)
  226. {
  227. GDclass *pm = m_Classes.Element(i);
  228. delete pm;
  229. }
  230. m_Classes.RemoveAll();
  231. }
  232. //-----------------------------------------------------------------------------
  233. // Purpose: Loads a gamedata (FGD) file into this object.
  234. // Input : pszFilename -
  235. // Output : Returns TRUE on success, FALSE on failure.
  236. //-----------------------------------------------------------------------------
  237. BOOL GameData::Load(const char *pszFilename)
  238. {
  239. TokenReader tr;
  240. if(GetFileAttributes(pszFilename) == 0xffffffff)
  241. return FALSE;
  242. if(!tr.Open(pszFilename))
  243. return FALSE;
  244. trtoken_t ttype;
  245. char szToken[128];
  246. while (1)
  247. {
  248. if (tr.GetErrorCount() >= MAX_ERRORS)
  249. {
  250. break;
  251. }
  252. ttype = tr.NextToken(szToken, sizeof(szToken));
  253. if(ttype == TOKENEOF)
  254. break;
  255. if(ttype != OPERATOR || !IsToken(szToken, "@"))
  256. {
  257. if(!GDError(tr, "expected @"))
  258. return FALSE;
  259. }
  260. // check what kind it is, and parse a new object
  261. if (tr.NextToken(szToken, sizeof(szToken)) != IDENT)
  262. {
  263. if(!GDError(tr, "expected identifier after @"))
  264. return FALSE;
  265. }
  266. if (IsToken(szToken, "baseclass") || IsToken(szToken, "pointclass") || IsToken(szToken, "solidclass") || IsToken(szToken, "keyframeclass") ||
  267. IsToken(szToken, "moveclass") || IsToken(szToken, "npcclass") || IsToken(szToken, "filterclass"))
  268. {
  269. //
  270. // New class.
  271. //
  272. GDclass *pNewClass = new GDclass;
  273. if (!pNewClass->InitFromTokens(tr, this))
  274. {
  275. tr.IgnoreTill(OPERATOR, "@"); // go to next section
  276. delete pNewClass;
  277. }
  278. else
  279. {
  280. if (IsToken(szToken, "baseclass")) // Not directly available to user.
  281. {
  282. pNewClass->SetBaseClass(true);
  283. }
  284. else if (IsToken(szToken, "pointclass")) // Generic point class.
  285. {
  286. pNewClass->SetPointClass(true);
  287. }
  288. else if (IsToken(szToken, "solidclass")) // Tied to solids.
  289. {
  290. pNewClass->SetSolidClass(true);
  291. }
  292. else if (IsToken(szToken, "npcclass")) // NPC class - can be spawned by npc_maker.
  293. {
  294. pNewClass->SetPointClass(true);
  295. pNewClass->SetNPCClass(true);
  296. }
  297. else if (IsToken(szToken, "filterclass")) // Filter class - can be used as a filter
  298. {
  299. pNewClass->SetPointClass(true);
  300. pNewClass->SetFilterClass(true);
  301. }
  302. else if (IsToken(szToken, "moveclass")) // Animating
  303. {
  304. pNewClass->SetMoveClass(true);
  305. pNewClass->SetPointClass(true);
  306. }
  307. else if (IsToken(szToken, "keyframeclass")) // Animation keyframes
  308. {
  309. pNewClass->SetKeyFrameClass(true);
  310. pNewClass->SetPointClass(true);
  311. }
  312. // Check and see if this new class matches an existing one. If so we will override the previous definition.
  313. int nExistingClassIndex = 0;
  314. GDclass *pExistingClass = ClassForName(pNewClass->GetName(), &nExistingClassIndex);
  315. if (NULL != pExistingClass)
  316. {
  317. m_Classes.InsertAfter(nExistingClassIndex, pNewClass);
  318. m_Classes.Remove(nExistingClassIndex);
  319. }
  320. else
  321. {
  322. m_Classes.AddToTail(pNewClass);
  323. }
  324. }
  325. }
  326. else if (IsToken(szToken, "include"))
  327. {
  328. if (GDGetToken(tr, szToken, sizeof(szToken), STRING))
  329. {
  330. // Let's assume it's in the same directory.
  331. char justPath[MAX_PATH], loadFilename[MAX_PATH];
  332. if ( Q_ExtractFilePath( pszFilename, justPath, sizeof( justPath ) ) )
  333. {
  334. Q_snprintf( loadFilename, sizeof( loadFilename ), "%s%s", justPath, szToken );
  335. }
  336. else
  337. {
  338. Q_strncpy( loadFilename, szToken, sizeof( loadFilename ) );
  339. }
  340. // First try our fully specified directory
  341. if (!Load(loadFilename))
  342. {
  343. // Failing that, try our start directory
  344. if (!Load(szToken))
  345. {
  346. GDError(tr, "error including file: %s", szToken);
  347. }
  348. }
  349. }
  350. }
  351. else if (IsToken(szToken, "mapsize"))
  352. {
  353. if (!ParseMapSize(tr))
  354. {
  355. // Error in map size specifier, skip to next @ sign.
  356. tr.IgnoreTill(OPERATOR, "@");
  357. }
  358. }
  359. else if ( IsToken( szToken, "materialexclusion" ) )
  360. {
  361. if ( !LoadFGDMaterialExclusions( tr ) )
  362. {
  363. // FGD exclusions not defined; skip to next @ sign.
  364. tr.IgnoreTill(OPERATOR, "@");
  365. }
  366. }
  367. else if ( IsToken( szToken, "autovisgroup" ) )
  368. {
  369. if ( !LoadFGDAutoVisGroups( tr ) )
  370. {
  371. // FGD AutoVisGroups not defined; skip to next @ sign.
  372. tr.IgnoreTill(OPERATOR, "@");
  373. }
  374. }
  375. else
  376. {
  377. GDError(tr, "unrecognized section name %s", szToken);
  378. tr.IgnoreTill(OPERATOR, "@");
  379. }
  380. }
  381. if (tr.GetErrorCount() > 0)
  382. {
  383. return FALSE;
  384. }
  385. tr.Close();
  386. return TRUE;
  387. }
  388. //-----------------------------------------------------------------------------
  389. // Purpose: Parses the "mapsize" specifier, which should be of the form:
  390. //
  391. // mapsize(min, max)
  392. //
  393. // ex: mapsize(-8192, 8192)
  394. //
  395. // Input : tr -
  396. // Output : Returns true on success, false on failure.
  397. //-----------------------------------------------------------------------------
  398. bool GameData::ParseMapSize(TokenReader &tr)
  399. {
  400. if (!GDSkipToken(tr, OPERATOR, "("))
  401. {
  402. return false;
  403. }
  404. char szToken[128];
  405. if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER))
  406. {
  407. return false;
  408. }
  409. int nMin = atoi(szToken);
  410. if (!GDSkipToken(tr, OPERATOR, ","))
  411. {
  412. return false;
  413. }
  414. if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER))
  415. {
  416. return false;
  417. }
  418. int nMax = atoi(szToken);
  419. if (nMin != nMax)
  420. {
  421. m_nMinMapCoord = min(nMin, nMax);
  422. m_nMaxMapCoord = max(nMin, nMax);
  423. }
  424. if (!GDSkipToken(tr, OPERATOR, ")"))
  425. {
  426. return false;
  427. }
  428. return true;
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose:
  432. // Input : pszName -
  433. // piIndex -
  434. // Output :
  435. //-----------------------------------------------------------------------------
  436. GDclass *GameData::ClassForName(const char *pszName, int *piIndex)
  437. {
  438. int nCount = m_Classes.Count();
  439. for (int i = 0; i < nCount; i++)
  440. {
  441. GDclass *mp = m_Classes.Element(i);
  442. if(!strcmp(mp->GetName(), pszName))
  443. {
  444. if(piIndex)
  445. piIndex[0] = i;
  446. return mp;
  447. }
  448. }
  449. return NULL;
  450. }
  451. // These are 'standard' keys that every entity uses, but they aren't specified that way in the .fgd
  452. static const char *RequiredKeys[] =
  453. {
  454. "Origin",
  455. "Angles",
  456. NULL
  457. };
  458. //-----------------------------------------------------------------------------
  459. // Purpose: this function will set up the initial class about to be instanced
  460. // Input : pszClassName - the class name of the entity to be instanced
  461. // pszInstancePrefix - the prefix to be used for all name fields
  462. // Origin - the origin offset of the instance
  463. // Angles - the angle rotation of the instance
  464. // Output : if successful, will return the game data class of the class name
  465. //-----------------------------------------------------------------------------
  466. GDclass *GameData::BeginInstanceRemap( const char *pszClassName, const char *pszInstancePrefix, Vector &Origin, QAngle &Angle )
  467. {
  468. m_InstanceOrigin = Origin;
  469. m_InstanceAngle = Angle;
  470. AngleMatrix( m_InstanceAngle, m_InstanceOrigin, m_InstanceMat );
  471. strcpy( m_InstancePrefix, pszInstancePrefix );
  472. if ( m_InstanceClass )
  473. {
  474. delete m_InstanceClass;
  475. m_InstanceClass = NULL;
  476. }
  477. if ( strcmpi( pszClassName, "info_overlay_accessor" ) == 0 )
  478. { // yucky hack for a made up entity in the bsp process
  479. pszClassName = "info_overlay";
  480. }
  481. GDclass *BaseClass = ClassForName( pszClassName );
  482. if ( BaseClass )
  483. {
  484. m_InstanceClass = new GDclass();
  485. m_InstanceClass->Parent = this;
  486. m_InstanceClass->AddBase( BaseClass );
  487. for( int i = 0; RequiredKeys[ i ]; i++ )
  488. {
  489. if ( m_InstanceClass->VarForName( RequiredKeys[ i ] ) == NULL )
  490. {
  491. BaseClass = ClassForName( RequiredKeys[ i ] );
  492. if ( BaseClass )
  493. {
  494. m_InstanceClass->AddBase( BaseClass );
  495. }
  496. }
  497. }
  498. }
  499. else
  500. {
  501. m_InstanceClass = NULL;
  502. }
  503. return m_InstanceClass;
  504. }
  505. enum tRemapOperation
  506. {
  507. REMAP_NAME = 0,
  508. REMAP_POSITION,
  509. REMAP_ANGLE,
  510. REMAP_ANGLE_NEGATIVE_PITCH,
  511. };
  512. static CUtlMap< GDIV_TYPE, tRemapOperation > RemapOperation;
  513. //-----------------------------------------------------------------------------
  514. // Purpose: function to sort the class type for the RemapOperations map
  515. // Input : type1 - the first type to compare against
  516. // type2 - the second type to compare against
  517. // Output : returns true if the first type is less than the second one
  518. //-----------------------------------------------------------------------------
  519. static bool CUtlType_LessThan( const GDIV_TYPE &type1, const GDIV_TYPE &type2 )
  520. {
  521. return ( type1 < type2 );
  522. }
  523. //-----------------------------------------------------------------------------
  524. // Purpose: this function will attempt to remap a key's value
  525. // Input : pszKey - the name of the key
  526. // pszInvalue - the original value
  527. // AllowNameRemapping - only do name remapping if this parameter is true.
  528. // this is generally only false on the instance level.
  529. // Output : returns true if the value changed
  530. // pszOutValue - the new value if changed
  531. //-----------------------------------------------------------------------------
  532. bool GameData::RemapKeyValue( const char *pszKey, const char *pszInValue, char *pszOutValue, TNameFixup NameFixup )
  533. {
  534. if ( RemapOperation.Count() == 0 )
  535. {
  536. RemapOperation.SetLessFunc( &CUtlType_LessThan );
  537. RemapOperation.Insert( ivAngle, REMAP_ANGLE );
  538. RemapOperation.Insert( ivTargetDest, REMAP_NAME );
  539. RemapOperation.Insert( ivTargetSrc, REMAP_NAME );
  540. RemapOperation.Insert( ivOrigin, REMAP_POSITION );
  541. RemapOperation.Insert( ivAxis, REMAP_ANGLE );
  542. RemapOperation.Insert( ivAngleNegativePitch, REMAP_ANGLE_NEGATIVE_PITCH );
  543. }
  544. if ( !m_InstanceClass )
  545. {
  546. return false;
  547. }
  548. GDinputvariable *KVVar = m_InstanceClass->VarForName( pszKey );
  549. if ( !KVVar )
  550. {
  551. return false;
  552. }
  553. GDIV_TYPE KVType = KVVar->GetType();
  554. int KVRemapIndex = RemapOperation.Find( KVType );
  555. if ( KVRemapIndex == RemapOperation.InvalidIndex() )
  556. {
  557. return false;
  558. }
  559. strcpy( pszOutValue, pszInValue );
  560. switch( RemapOperation[ KVRemapIndex ] )
  561. {
  562. case REMAP_NAME:
  563. if ( KVType != ivInstanceVariable )
  564. {
  565. RemapNameField( pszInValue, pszOutValue, NameFixup );
  566. }
  567. break;
  568. case REMAP_POSITION:
  569. {
  570. Vector inPoint( 0.0f, 0.0f, 0.0f ), outPoint;
  571. sscanf ( pszInValue, "%f %f %f", &inPoint.x, &inPoint.y, &inPoint.z );
  572. VectorTransform( inPoint, m_InstanceMat, outPoint );
  573. sprintf( pszOutValue, "%g %g %g", outPoint.x, outPoint.y, outPoint.z );
  574. }
  575. break;
  576. case REMAP_ANGLE:
  577. if ( m_InstanceAngle.x != 0.0f || m_InstanceAngle.y != 0.0f || m_InstanceAngle.z != 0.0f )
  578. {
  579. QAngle inAngles( 0.0f, 0.0f, 0.0f ), outAngles;
  580. matrix3x4_t angToWorld, localMatrix;
  581. sscanf ( pszInValue, "%f %f %f", &inAngles.x, &inAngles.y, &inAngles.z );
  582. AngleMatrix( inAngles, angToWorld );
  583. MatrixMultiply( m_InstanceMat, angToWorld, localMatrix );
  584. MatrixAngles( localMatrix, outAngles );
  585. sprintf( pszOutValue, "%g %g %g", outAngles.x, outAngles.y, outAngles.z );
  586. }
  587. break;
  588. case REMAP_ANGLE_NEGATIVE_PITCH:
  589. if ( m_InstanceAngle.x != 0.0f || m_InstanceAngle.y != 0.0f || m_InstanceAngle.z != 0.0f )
  590. {
  591. QAngle inAngles( 0.0f, 0.0f, 0.0f ), outAngles;
  592. matrix3x4_t angToWorld, localMatrix;
  593. sscanf ( pszInValue, "%f", &inAngles.x ); // just the pitch
  594. inAngles.x = -inAngles.x;
  595. AngleMatrix( inAngles, angToWorld );
  596. MatrixMultiply( m_InstanceMat, angToWorld, localMatrix );
  597. MatrixAngles( localMatrix, outAngles );
  598. sprintf( pszOutValue, "%g", -outAngles.x ); // just the pitch
  599. }
  600. break;
  601. }
  602. return ( strcmpi( pszInValue, pszOutValue ) != 0 );
  603. }
  604. //-----------------------------------------------------------------------------
  605. // Purpose: this function will attempt to remap a name field.
  606. // Input : pszInvalue - the original value
  607. // AllowNameRemapping - only do name remapping if this parameter is true.
  608. // this is generally only false on the instance level.
  609. // Output : returns true if the value changed
  610. // pszOutValue - the new value if changed
  611. //-----------------------------------------------------------------------------
  612. bool GameData::RemapNameField( const char *pszInValue, char *pszOutValue, TNameFixup NameFixup )
  613. {
  614. strcpy( pszOutValue, pszInValue );
  615. if ( pszInValue[ 0 ] && pszInValue[ 0 ] != '@' )
  616. { // ! at the start of a value means it is global and should not be remaped
  617. switch( NameFixup )
  618. {
  619. case NAME_FIXUP_PREFIX:
  620. sprintf( pszOutValue, "%s-%s", m_InstancePrefix, pszInValue );
  621. break;
  622. case NAME_FIXUP_POSTFIX:
  623. sprintf( pszOutValue, "%s-%s", pszInValue, m_InstancePrefix );
  624. break;
  625. }
  626. }
  627. return ( strcmpi( pszInValue, pszOutValue ) != 0 );
  628. }
  629. //-----------------------------------------------------------------------------
  630. // Purpose: Gathers any FGD-defined material directory exclusions
  631. // Input :
  632. // Output :
  633. //-----------------------------------------------------------------------------
  634. bool GameData::LoadFGDMaterialExclusions( TokenReader &tr )
  635. {
  636. if ( !GDSkipToken( tr, OPERATOR, "[" ) )
  637. {
  638. return false;
  639. }
  640. while ( 1 )
  641. {
  642. char szToken[128];
  643. bool bMatchFound = false;
  644. if ( tr.PeekTokenType( szToken, sizeof( szToken ) ) == OPERATOR )
  645. {
  646. break;
  647. }
  648. else if ( GDGetToken( tr, szToken, sizeof( szToken ), STRING ) )
  649. {
  650. // Make sure we haven't loaded this from another FGD
  651. for ( int i = 0; i < m_FGDMaterialExclusions.Count(); i++ )
  652. {
  653. if ( !stricmp( szToken, m_FGDMaterialExclusions[i].szDirectory ) )
  654. {
  655. bMatchFound = true;
  656. break;
  657. }
  658. }
  659. // Parse the string
  660. if ( bMatchFound == false )
  661. {
  662. int index = m_FGDMaterialExclusions.AddToTail();
  663. Q_strncpy( m_FGDMaterialExclusions[index].szDirectory, szToken, sizeof( m_FGDMaterialExclusions[index].szDirectory ) );
  664. m_FGDMaterialExclusions[index].bUserGenerated = false;
  665. }
  666. }
  667. }
  668. //
  669. // Closing square brace.
  670. //
  671. if ( !GDSkipToken( tr, OPERATOR, "]" ) )
  672. {
  673. return( FALSE );
  674. }
  675. return true;
  676. }
  677. //-----------------------------------------------------------------------------
  678. // Purpose: Gathers any FGD-defined Auto VisGroups
  679. // Input :
  680. // Output :
  681. //-----------------------------------------------------------------------------
  682. bool GameData::LoadFGDAutoVisGroups( TokenReader &tr )
  683. {
  684. int gindex = 0; // Index of AutoVisGroups
  685. int cindex = 0; // Index of Classes
  686. char szToken[128];
  687. // Handle the Parent -- World Geometry, Entities, World Detail
  688. if ( GDSkipToken( tr, OPERATOR, "=" ) )
  689. {
  690. // We expect a name
  691. if ( !GDGetToken( tr, szToken, sizeof( szToken ), STRING ) )
  692. {
  693. return( FALSE );
  694. }
  695. gindex = m_FGDAutoVisGroups.AddToTail();
  696. Q_strncpy( m_FGDAutoVisGroups[gindex].szParent, szToken, sizeof( m_FGDAutoVisGroups[gindex].szParent ) );
  697. // We expect a Class
  698. if ( !GDSkipToken( tr, OPERATOR, "[" ) )
  699. {
  700. return( FALSE );
  701. }
  702. }
  703. // Handle the Class(es) -- Brush Entities, Occluders, Lights
  704. while ( 1 )
  705. {
  706. if ( GDGetToken( tr, szToken, sizeof( szToken ), STRING ) )
  707. {
  708. cindex = m_FGDAutoVisGroups[gindex].m_Classes.AddToTail();
  709. Q_strncpy( m_FGDAutoVisGroups[gindex].m_Classes[cindex].szClass, szToken, sizeof( m_FGDAutoVisGroups[gindex].m_Classes[cindex].szClass ) );
  710. if ( !GDSkipToken( tr, OPERATOR, "[" ) )
  711. {
  712. return( FALSE );
  713. }
  714. // Parse objects/entities -- func_detail, point_template, light_spot
  715. while ( 1 )
  716. {
  717. if ( tr.PeekTokenType( szToken, sizeof( szToken ) ) == OPERATOR )
  718. {
  719. break;
  720. }
  721. if ( !GDGetToken( tr, szToken, sizeof( szToken ), STRING ) )
  722. {
  723. return( FALSE );
  724. }
  725. m_FGDAutoVisGroups[gindex].m_Classes[cindex].szEntities.CopyAndAddToTail( szToken );
  726. }
  727. if ( !GDSkipToken( tr, OPERATOR, "]" ) )
  728. {
  729. return( FALSE );
  730. }
  731. // See if we have another Class coming up
  732. if ( tr.PeekTokenType( szToken, sizeof( szToken ) ) == STRING )
  733. {
  734. continue;
  735. }
  736. // If no more Classes, we now expect a terminating ']'
  737. if ( !GDSkipToken( tr, OPERATOR, "]" ) )
  738. {
  739. return( FALSE );
  740. }
  741. // We're done
  742. return true;
  743. }
  744. // We don't have another Class; look for a terminating brace
  745. else
  746. {
  747. if ( !GDSkipToken( tr, OPERATOR, "]" ) )
  748. {
  749. return( FALSE );
  750. }
  751. }
  752. }
  753. // Safety net
  754. GDError( tr, "Malformed AutoVisGroup -- Last processed: %s", szToken );
  755. return( FALSE );
  756. }
  757. // memdbgon must be the last include file in a .cpp file!!!
  758. #include "tier0/memdbgoff.h"