Counter Strike : Global Offensive Source Code
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.

620 lines
19 KiB

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