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.

629 lines
19 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "importkeyvaluebase.h"
  7. #include "dmserializers.h"
  8. #include "datamodel/idatamodel.h"
  9. #include "datamodel/dmelement.h"
  10. #include "tier1/KeyValues.h"
  11. #include "tier1/utlbuffer.h"
  12. #include "datamodel/dmattribute.h"
  13. //-----------------------------------------------------------------------------
  14. // Serialization class for VMF files (map files)
  15. //-----------------------------------------------------------------------------
  16. class CImportVMF : public CImportKeyValueBase
  17. {
  18. public:
  19. virtual const char *GetName() const { return "vmf"; }
  20. virtual const char *GetDescription() const { return "Valve Map File"; }
  21. virtual int GetCurrentVersion() const { return 0; } // doesn't store a version
  22. bool Serialize( CUtlBuffer &outBuf, CDmElement *pRoot );
  23. CDmElement* UnserializeFromKeyValues( KeyValues *pKeyValues );
  24. private:
  25. // Reads a single entity
  26. bool UnserializeEntityKey( CDmAttribute *pEntities, KeyValues *pKeyValues );
  27. // Reads entity editor keys
  28. bool UnserializeEntityEditorKey( CDmAttribute *pEditor, KeyValues *pKeyValues );
  29. // Reads keys that we currently do nothing with
  30. bool UnserializeUnusedKeys( DmElementHandle_t hOther, KeyValues *pKeyValues );
  31. // Writes out all everything other than entities
  32. bool SerializeOther( CUtlBuffer &buf, CDmAttribute *pOther, const char **ppFilter = 0 );
  33. // Writes out all entities
  34. bool SerializeEntities( CUtlBuffer &buf, CDmAttribute *pEntities );
  35. // Writes out a single attribute recursively
  36. bool SerializeAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, bool bElementArrays );
  37. // Writes entity editor keys
  38. bool SerializeEntityEditorKey( CUtlBuffer &buf, DmElementHandle_t hEditor );
  39. // Updates the max hammer id
  40. void UpdateMaxHammerId( KeyValues *pKeyValue );
  41. // Max id read from the file
  42. int m_nMaxHammerId;
  43. };
  44. //-----------------------------------------------------------------------------
  45. // Singleton instance
  46. //-----------------------------------------------------------------------------
  47. static CImportVMF s_ImportVMF;
  48. void InstallVMFImporter( IDataModel *pFactory )
  49. {
  50. pFactory->AddSerializer( &s_ImportVMF );
  51. }
  52. //-----------------------------------------------------------------------------
  53. // Deals with poorly-named key values for the DME system
  54. //-----------------------------------------------------------------------------
  55. static const char *s_pKeyRemapNames[][2] =
  56. {
  57. { "id", "__id" },
  58. { "name", "__name" },
  59. { "type", "__type" },
  60. { NULL, NULL },
  61. };
  62. //-----------------------------------------------------------------------------
  63. // Gets remap name for unserialization/serailzation
  64. //-----------------------------------------------------------------------------
  65. static const char *GetRemapName( const char *pName, bool bSerialization )
  66. {
  67. for ( int i = 0; s_pKeyRemapNames[i][0]; ++i )
  68. {
  69. if ( !Q_stricmp( pName, s_pKeyRemapNames[i][bSerialization] ) )
  70. return s_pKeyRemapNames[i][1 - bSerialization];
  71. }
  72. return pName;
  73. }
  74. //-----------------------------------------------------------------------------
  75. // Writes out a single attribute recursively
  76. //-----------------------------------------------------------------------------
  77. bool CImportVMF::SerializeAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, bool bElementArrays )
  78. {
  79. if ( pAttribute->IsFlagSet( FATTRIB_STANDARD | FATTRIB_DONTSAVE ) )
  80. return true;
  81. const char *pFieldName = GetRemapName( pAttribute->GetName(), true );
  82. if ( !Q_stricmp( pFieldName, "editorType" ) )
  83. return true;
  84. if ( !IsArrayType( pAttribute->GetType() ) )
  85. {
  86. if ( !bElementArrays )
  87. {
  88. buf.Printf( "\"%s\" ", pFieldName );
  89. if ( pAttribute->GetType() != AT_STRING )
  90. {
  91. buf.Printf( "\"" );
  92. }
  93. g_pDataModel->SetSerializationDelimiter( GetCStringCharConversion() );
  94. pAttribute->Serialize( buf );
  95. g_pDataModel->SetSerializationDelimiter( NULL );
  96. if ( pAttribute->GetType() != AT_STRING )
  97. {
  98. buf.Printf( "\"" );
  99. }
  100. buf.Printf( "\n" );
  101. }
  102. }
  103. else
  104. {
  105. if ( bElementArrays )
  106. {
  107. Assert( pAttribute->GetType() == AT_ELEMENT_ARRAY );
  108. if ( !SerializeOther( buf, pAttribute ) )
  109. return false;
  110. }
  111. }
  112. return true;
  113. }
  114. //-----------------------------------------------------------------------------
  115. // Writes out all everything other than entities
  116. //-----------------------------------------------------------------------------
  117. bool CImportVMF::SerializeOther( CUtlBuffer &buf, CDmAttribute *pOther, const char **ppFilter )
  118. {
  119. CDmrElementArray<> array( pOther );
  120. int nCount = array.Count();
  121. for ( int i = 0; i < nCount; ++i )
  122. {
  123. CDmElement *pElement = array[i];
  124. const char *pElementName = pElement->GetName();
  125. if ( ppFilter )
  126. {
  127. int j;
  128. for ( j = 0; ppFilter[j]; ++j )
  129. {
  130. if ( !Q_stricmp( pElementName, ppFilter[j] ) )
  131. break;
  132. }
  133. if ( !ppFilter[j] )
  134. continue;
  135. }
  136. int nLen = Q_strlen( pElementName ) + 1;
  137. char *pTemp = (char*)_alloca( nLen );
  138. Q_strncpy( pTemp, pElementName, nLen );
  139. Q_strlower( pTemp );
  140. buf.Printf( "%s\n", pTemp );
  141. buf.Printf( "{\n" );
  142. buf.PushTab();
  143. // Normal attributes first
  144. for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() )
  145. {
  146. if ( !SerializeAttribute( buf, pAttribute, false ) )
  147. return false;
  148. }
  149. // Subkeys later
  150. for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() )
  151. {
  152. if ( !SerializeAttribute( buf, pAttribute, true ) )
  153. return false;
  154. }
  155. buf.PopTab();
  156. buf.Printf( "}\n" );
  157. }
  158. return true;
  159. }
  160. //-----------------------------------------------------------------------------
  161. // Writes entity editor keys
  162. //-----------------------------------------------------------------------------
  163. bool CImportVMF::SerializeEntityEditorKey( CUtlBuffer &buf, DmElementHandle_t hEditor )
  164. {
  165. CDmElement *pEditorElement = g_pDataModel->GetElement( hEditor );
  166. if ( !pEditorElement )
  167. return true;
  168. buf.Printf( "editor\n" );
  169. buf.Printf( "{\n" );
  170. buf.PushTab();
  171. {
  172. CDmAttribute *pAttribute = pEditorElement->GetAttribute( "color" );
  173. if ( pAttribute )
  174. {
  175. Color c = pAttribute->GetValue<Color>();
  176. buf.Printf( "\"color\" \"%d %d %d\"\n", c.r(), c.g(), c.b() );
  177. }
  178. }
  179. PrintIntAttribute( pEditorElement, buf, "id" ); // FIXME - id is a DmObjectId_t!!! This should never print anything!
  180. PrintStringAttribute( pEditorElement, buf, "comments" );
  181. PrintBoolAttribute( pEditorElement, buf, "visgroupshown" );
  182. PrintBoolAttribute( pEditorElement, buf, "visgroupautoshown" );
  183. for ( CDmAttribute *pAttribute = pEditorElement->FirstAttribute(); pAttribute != NULL; pAttribute = pAttribute->NextAttribute() )
  184. {
  185. if ( pAttribute->IsFlagSet( FATTRIB_STANDARD | FATTRIB_DONTSAVE ) )
  186. continue;
  187. const char *pKeyName = pAttribute->GetName();
  188. if ( Q_stricmp( pKeyName, "color" ) && Q_stricmp( pKeyName, "id" ) &&
  189. Q_stricmp( pKeyName, "comments" ) && Q_stricmp( pKeyName, "visgroupshown" ) &&
  190. Q_stricmp( pKeyName, "visgroupautoshown" ) )
  191. {
  192. PrintStringAttribute( pEditorElement, buf, pKeyName );
  193. }
  194. }
  195. buf.PopTab();
  196. buf.Printf( "}\n" );
  197. return true;
  198. }
  199. //-----------------------------------------------------------------------------
  200. // Writes out all entities
  201. //-----------------------------------------------------------------------------
  202. bool CImportVMF::SerializeEntities( CUtlBuffer &buf, CDmAttribute *pEntities )
  203. {
  204. // FIXME: Make this serialize in the order in which it appears in the FGD
  205. // to minimize diffs
  206. CDmrElementArray<> array( pEntities );
  207. int nCount = array.Count();
  208. for ( int i = 0; i < nCount; ++i )
  209. {
  210. CDmElement *pElement = array[i];
  211. buf.Printf( "entity\n" );
  212. buf.Printf( "{\n" );
  213. buf.PushTab();
  214. buf.Printf( "\"id\" \"%s\"\n", pElement->GetName() );
  215. for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() )
  216. {
  217. // Do 'editor' at the end to preserve ordering and not make terrible diffs
  218. if ( !Q_stricmp( pAttribute->GetName(), "editor" ) )
  219. continue;
  220. if ( !SerializeAttribute( buf, pAttribute, false ) )
  221. return false;
  222. }
  223. for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() )
  224. {
  225. // Do 'editor' at the end to preserve ordering and not make terrible diffs
  226. if ( !Q_stricmp( pAttribute->GetName(), "editor" ) )
  227. continue;
  228. if ( !SerializeAttribute( buf, pAttribute, true ) )
  229. return false;
  230. }
  231. // Do the 'editor'
  232. CDmAttribute *pEditor = pElement->GetAttribute( "editor" );
  233. if ( pEditor )
  234. {
  235. SerializeEntityEditorKey( buf, pEditor->GetValue<DmElementHandle_t>() );
  236. }
  237. buf.PopTab();
  238. buf.Printf( "}\n" );
  239. }
  240. return true;
  241. }
  242. //-----------------------------------------------------------------------------
  243. // Writes out a new VMF file
  244. //-----------------------------------------------------------------------------
  245. bool CImportVMF::Serialize( CUtlBuffer &buf, CDmElement *pRoot )
  246. {
  247. // This is done in this strange way (namely, serializing other twice) to minimize diffs
  248. const char *pOtherFilter1[] =
  249. {
  250. "versioninfo", "visgroups", "viewsettings", "world", NULL
  251. };
  252. const char *pOtherFilter2[] =
  253. {
  254. "cameras", "cordon", "hidden", NULL
  255. };
  256. CDmAttribute *pOther = pRoot->GetAttribute( "other" );
  257. if ( pOther && pOther->GetType() == AT_ELEMENT_ARRAY )
  258. {
  259. if ( !SerializeOther( buf, pOther, pOtherFilter1 ) )
  260. return false;
  261. }
  262. // Serialize entities
  263. CDmAttribute *pEntities = pRoot->GetAttribute( "entities" );
  264. if ( pEntities && pEntities->GetType() == AT_ELEMENT_ARRAY )
  265. {
  266. if ( !SerializeEntities( buf, pEntities ) )
  267. return false;
  268. }
  269. if ( pOther && pOther->GetType() == AT_ELEMENT_ARRAY )
  270. {
  271. if ( !SerializeOther( buf, pOther, pOtherFilter2 ) )
  272. return false;
  273. }
  274. return true;
  275. }
  276. //-----------------------------------------------------------------------------
  277. // Updates the max hammer id
  278. //-----------------------------------------------------------------------------
  279. void CImportVMF::UpdateMaxHammerId( KeyValues *pField )
  280. {
  281. if ( !Q_stricmp( pField->GetName(), "id" ) )
  282. {
  283. int nId = atoi( pField->GetString() );
  284. if ( nId > m_nMaxHammerId )
  285. {
  286. m_nMaxHammerId = nId;
  287. }
  288. }
  289. }
  290. //-----------------------------------------------------------------------------
  291. // Reads entity editor keys
  292. //-----------------------------------------------------------------------------
  293. bool CImportVMF::UnserializeEntityEditorKey( CDmAttribute *pEditorAttribute, KeyValues *pKeyValues )
  294. {
  295. CDmElement *pEditor;
  296. DmElementHandle_t hEditor = pEditorAttribute->GetValue<DmElementHandle_t>();
  297. if ( hEditor == DMELEMENT_HANDLE_INVALID )
  298. {
  299. pEditor = CreateDmElement( "DmElement", "editor", NULL );;
  300. if ( !pEditor )
  301. return false;
  302. hEditor = pEditor->GetHandle();
  303. pEditorAttribute->SetValue( hEditor );
  304. }
  305. else
  306. {
  307. pEditor = g_pDataModel->GetElement( hEditor );
  308. }
  309. int r, g, b;
  310. if ( sscanf( pKeyValues->GetString( "color", "" ), "%d %d %d", &r, &g, &b ) == 3 )
  311. {
  312. Color c( r, g, b, 255 );
  313. if ( !pEditor->SetValue( "color", c ) )
  314. return false;
  315. }
  316. KeyValues *pIdKey = pKeyValues->FindKey( "id" );
  317. if ( pIdKey )
  318. {
  319. UpdateMaxHammerId( pIdKey );
  320. }
  321. AddIntAttribute( pEditor, pKeyValues, "id" );
  322. AddStringAttribute( pEditor, pKeyValues, "comments" );
  323. AddBoolAttribute( pEditor, pKeyValues, "visgroupshown" );
  324. AddBoolAttribute( pEditor, pKeyValues, "visgroupautoshown" );
  325. for ( KeyValues *pUserKey = pKeyValues->GetFirstValue(); pUserKey != NULL; pUserKey = pUserKey->GetNextValue() )
  326. {
  327. const char *pKeyName = pUserKey->GetName();
  328. if ( Q_stricmp( pKeyName, "color" ) && Q_stricmp( pKeyName, "id" ) &&
  329. Q_stricmp( pKeyName, "comments" ) && Q_stricmp( pKeyName, "visgroupshown" ) &&
  330. Q_stricmp( pKeyName, "visgroupautoshown" ) )
  331. {
  332. AddStringAttribute( pEditor, pKeyValues, pKeyName );
  333. }
  334. }
  335. return true;
  336. }
  337. //-----------------------------------------------------------------------------
  338. // Reads a single entity
  339. //-----------------------------------------------------------------------------
  340. bool CImportVMF::UnserializeEntityKey( CDmAttribute *pEntities, KeyValues *pKeyValues )
  341. {
  342. CDmElement *pEntity = CreateDmElement( "DmeVMFEntity", pKeyValues->GetString( "id", "-1" ), NULL );
  343. if ( !pEntity )
  344. return false;
  345. CDmrElementArray<> array( pEntities );
  346. array.AddToTail( pEntity );
  347. // Each act busy needs to have an editortype associated with it so it displays nicely in editors
  348. pEntity->SetValue( "editorType", "vmfEntity" );
  349. const char *pClassName = pKeyValues->GetString( "classname", NULL );
  350. if ( !pClassName )
  351. return false;
  352. // Read the actual fields
  353. for ( KeyValues *pField = pKeyValues->GetFirstValue(); pField != NULL; pField = pField->GetNextValue() )
  354. {
  355. // FIXME: Knowing the FGD here would be useful for type determination.
  356. // Look up the field by name based on class name
  357. // In the meantime, just use the keyvalues type?
  358. char pFieldName[512];
  359. Q_strncpy( pFieldName, pField->GetName(), sizeof(pFieldName) );
  360. Q_strlower( pFieldName );
  361. // Don't do id: it's used as the name
  362. // Not to mention it's a protected name
  363. if ( !Q_stricmp( pFieldName, "id" ) )
  364. {
  365. UpdateMaxHammerId( pField );
  366. continue;
  367. }
  368. // Type, name, and editortype are protected names
  369. Assert( Q_stricmp( pFieldName, "type" ) && Q_stricmp( pFieldName, "name" ) && Q_stricmp( pFieldName, "editortype" ) );
  370. switch( pField->GetDataType() )
  371. {
  372. case KeyValues::TYPE_INT:
  373. if ( !AddIntAttributeFlags( pEntity, pKeyValues, pFieldName, FATTRIB_USERDEFINED ) )
  374. return false;
  375. break;
  376. case KeyValues::TYPE_FLOAT:
  377. if ( !AddFloatAttributeFlags( pEntity, pKeyValues, pFieldName, FATTRIB_USERDEFINED ) )
  378. return false;
  379. break;
  380. case KeyValues::TYPE_STRING:
  381. {
  382. const char* pString = pField->GetString();
  383. if (!pString || !pString[0])
  384. return false;
  385. // Look for vectors
  386. Vector4D v;
  387. if ( sscanf( pString, "%f %f %f %f", &v.x, &v.y, &v.z, &v.w ) == 4 )
  388. {
  389. if ( !pEntity->SetValue( pFieldName, v ) )
  390. return false;
  391. CDmAttribute *pAttribute = pEntity->GetAttribute( pFieldName );
  392. pAttribute->AddFlag( FATTRIB_USERDEFINED );
  393. }
  394. else if ( sscanf( pString, "%f %f %f", &v.x, &v.y, &v.z ) == 3 )
  395. {
  396. if ( !pEntity->SetValue( pFieldName, v.AsVector3D() ) )
  397. {
  398. QAngle ang( v.x, v.y, v.z );
  399. if ( !pEntity->SetValue( pFieldName, ang ) )
  400. return false;
  401. }
  402. CDmAttribute *pAttribute = pEntity->GetAttribute( pFieldName );
  403. pAttribute->AddFlag( FATTRIB_USERDEFINED );
  404. }
  405. else
  406. {
  407. if ( !AddStringAttributeFlags( pEntity, pKeyValues, pFieldName, FATTRIB_USERDEFINED ) )
  408. return false;
  409. }
  410. }
  411. break;
  412. }
  413. }
  414. // Read the subkeys
  415. CDmAttribute *pEditor = pEntity->AddAttribute( "editor", AT_ELEMENT );
  416. CDmrElementArray<> otherKeys( pEntity->AddAttribute( "other", AT_ELEMENT_ARRAY ) );
  417. for ( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextTrueSubKey() )
  418. {
  419. bool bOk = false;
  420. if ( !Q_stricmp( pSubKey->GetName(), "editor" ) )
  421. {
  422. bOk = UnserializeEntityEditorKey( pEditor, pSubKey );
  423. }
  424. else
  425. {
  426. // We don't currently do anything with the other keys
  427. CDmElement *pOther = CreateDmElement( "DmElement", pSubKey->GetName(), NULL );
  428. otherKeys.AddToTail( pOther );
  429. bOk = UnserializeUnusedKeys( pOther->GetHandle(), pSubKey );
  430. }
  431. if ( !bOk )
  432. return false;
  433. }
  434. return true;
  435. }
  436. //-----------------------------------------------------------------------------
  437. // Reads keys that we currently do nothing with
  438. //-----------------------------------------------------------------------------
  439. bool CImportVMF::UnserializeUnusedKeys( DmElementHandle_t hOther, KeyValues *pKeyValues )
  440. {
  441. CDmElement *pOther = g_pDataModel->GetElement( hOther );
  442. // Read the actual fields
  443. for ( KeyValues *pField = pKeyValues->GetFirstValue(); pField != NULL; pField = pField->GetNextValue() )
  444. {
  445. UpdateMaxHammerId( pField );
  446. const char *pFieldName = GetRemapName( pField->GetName(), false );
  447. pOther->SetValue( pFieldName, pField->GetString() );
  448. }
  449. // Read the subkeys
  450. CDmrElementArray<> subKeys( pOther->AddAttribute( "subkeys", AT_ELEMENT_ARRAY ) );
  451. for ( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextTrueSubKey() )
  452. {
  453. CDmElement *pSubElement = CreateDmElement( "DmElement", pSubKey->GetName(), NULL );
  454. subKeys.AddToTail( pSubElement );
  455. if ( !UnserializeUnusedKeys( pSubElement->GetHandle(), pSubKey ) )
  456. return false;
  457. }
  458. return true;
  459. }
  460. /*
  461. //-----------------------------------------------------------------------------
  462. // Reads the cordon data
  463. //-----------------------------------------------------------------------------
  464. bool CImportVMF::UnserializeCordonKey( IDmAttributeElement *pCordon, KeyValues *pKeyValues )
  465. {
  466. DmElementHandle_t hCordon = pCordon->GetValue().Get();
  467. if ( hCordon == DMELEMENT_HANDLE_INVALID )
  468. {
  469. hCordon = CreateDmElement( "DmElement", "cordon", NULL );
  470. if ( hCordon == DMELEMENT_HANDLE_INVALID )
  471. return false;
  472. pCordon->SetValue( hCordon );
  473. }
  474. AddBoolAttribute( hCordon, pKeyValues, "active" );
  475. Vector v;
  476. if ( sscanf( pKeyValues->GetString( "mins", "" ), "(%f %f %f)", &v.x, &v.y, &v.z ) == 3 )
  477. {
  478. if ( !DmElementAddAttribute( hCordon, "mins", v ) )
  479. return false;
  480. }
  481. if ( sscanf( pKeyValues->GetString( "maxs", "" ), "(%f %f %f)", &v.x, &v.y, &v.z ) == 3 )
  482. {
  483. if ( !DmElementAddAttribute( hCordon, "maxs", v ) )
  484. return false;
  485. }
  486. return true;
  487. }
  488. */
  489. //-----------------------------------------------------------------------------
  490. // Main entry point for the unserialization
  491. //-----------------------------------------------------------------------------
  492. CDmElement* CImportVMF::UnserializeFromKeyValues( KeyValues *pKeyValues )
  493. {
  494. m_nMaxHammerId = 0;
  495. // Create the main element
  496. CDmElement *pElement = CreateDmElement( "DmElement", "VMF", NULL );
  497. if ( !pElement )
  498. return NULL;
  499. // Each vmf needs to have an editortype associated with it so it displays nicely in editors
  500. pElement->SetValue( "editorType", "VMF" );
  501. // The VMF is a series of keyvalue blocks; either
  502. // 'entity', 'cameras', 'cordon', 'world', 'versioninfo', or 'viewsettings'
  503. CDmAttribute *pEntityArray = pElement->AddAttribute( "entities", AT_ELEMENT_ARRAY );
  504. // All main keys are root keys
  505. CDmrElementArray<> otherKeys( pElement->AddAttribute( "other", AT_ELEMENT_ARRAY ) );
  506. for ( ; pKeyValues != NULL; pKeyValues = pKeyValues->GetNextKey() )
  507. {
  508. bool bOk = false;
  509. if ( !Q_stricmp( pKeyValues->GetName(), "entity" ) )
  510. {
  511. bOk = UnserializeEntityKey( pEntityArray, pKeyValues );
  512. }
  513. else
  514. {
  515. // We don't currently do anything with
  516. CDmElement *pOther = CreateDmElement( "DmElement", pKeyValues->GetName(), NULL );
  517. otherKeys.AddToTail( pOther );
  518. bOk = UnserializeUnusedKeys( pOther->GetHandle(), pKeyValues );
  519. }
  520. if ( !bOk )
  521. {
  522. Warning( "Error importing VMF element %s\n", pKeyValues->GetName() );
  523. return NULL;
  524. }
  525. }
  526. // Resolve all element references recursively
  527. RecursivelyResolveElement( pElement );
  528. // Add the max id read in from the file to the root entity
  529. pElement->SetValue( "maxHammerId", m_nMaxHammerId );
  530. return pElement;
  531. }