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.

630 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "vcdblockdoc.h"
  9. #include "tier1/KeyValues.h"
  10. #include "tier1/utlbuffer.h"
  11. #include "toolutils/enginetools_int.h"
  12. #include "filesystem.h"
  13. #include "vcdblocktool.h"
  14. #include "toolframework/ienginetool.h"
  15. #include "dmevmfentity.h"
  16. #include "datamodel/idatamodel.h"
  17. #include "toolutils/attributeelementchoicelist.h"
  18. #include "infotargetbrowserpanel.h"
  19. #include "vgui_controls/messagebox.h"
  20. //-----------------------------------------------------------------------------
  21. // Constructor
  22. //-----------------------------------------------------------------------------
  23. CVcdBlockDoc::CVcdBlockDoc( IVcdBlockDocCallback *pCallback ) : m_pCallback( pCallback )
  24. {
  25. m_hVMFRoot = NULL;
  26. m_hEditRoot = NULL;
  27. m_pBSPFileName[0] = 0;
  28. m_pVMFFileName[0] = 0;
  29. m_pEditFileName[0] = 0;
  30. m_bDirty = false;
  31. g_pDataModel->InstallNotificationCallback( this );
  32. }
  33. CVcdBlockDoc::~CVcdBlockDoc()
  34. {
  35. g_pDataModel->RemoveNotificationCallback( this );
  36. }
  37. //-----------------------------------------------------------------------------
  38. // Inherited from INotifyUI
  39. //-----------------------------------------------------------------------------
  40. void CVcdBlockDoc::NotifyDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags )
  41. {
  42. OnDataChanged( pReason, nNotifySource, nNotifyFlags );
  43. }
  44. //-----------------------------------------------------------------------------
  45. // Gets the file name
  46. //-----------------------------------------------------------------------------
  47. const char *CVcdBlockDoc::GetBSPFileName()
  48. {
  49. return m_pBSPFileName;
  50. }
  51. const char *CVcdBlockDoc::GetVMFFileName()
  52. {
  53. return m_pVMFFileName;
  54. }
  55. void CVcdBlockDoc::SetVMFFileName( const char *pFileName )
  56. {
  57. Q_strncpy( m_pVMFFileName, pFileName, sizeof( m_pVMFFileName ) );
  58. Q_FixSlashes( m_pVMFFileName );
  59. SetDirty( true );
  60. }
  61. const char *CVcdBlockDoc::GetEditFileName()
  62. {
  63. return m_pEditFileName;
  64. }
  65. void CVcdBlockDoc::SetEditFileName( const char *pFileName )
  66. {
  67. Q_strncpy( m_pEditFileName, pFileName, sizeof( m_pEditFileName ) );
  68. Q_FixSlashes( m_pEditFileName );
  69. SetDirty( true );
  70. }
  71. //-----------------------------------------------------------------------------
  72. // Dirty bits
  73. //-----------------------------------------------------------------------------
  74. void CVcdBlockDoc::SetDirty( bool bDirty )
  75. {
  76. m_bDirty = bDirty;
  77. }
  78. bool CVcdBlockDoc::IsDirty() const
  79. {
  80. return m_bDirty;
  81. }
  82. //-----------------------------------------------------------------------------
  83. // Saves/loads from file
  84. //-----------------------------------------------------------------------------
  85. bool CVcdBlockDoc::LoadFromFile( const char *pFileName )
  86. {
  87. Assert( !m_hVMFRoot.Get() );
  88. Assert( !m_hEditRoot.Get() );
  89. CAppDisableUndoScopeGuard guard( "CVcdBlockDoc::LoadFromFile", NOTIFY_CHANGE_OTHER );
  90. SetDirty( false );
  91. if ( !pFileName[0] )
  92. return false;
  93. // Construct VMF file name from the BSP
  94. const char *pGame = Q_stristr( pFileName, "\\game\\" );
  95. if ( !pGame )
  96. {
  97. pGame = Q_stristr( pFileName, "\\content\\" );
  98. if ( !pGame )
  99. return false;
  100. }
  101. // Compute the map name
  102. const char *pMaps = Q_stristr( pFileName, "\\maps\\" );
  103. if ( !pMaps )
  104. return false;
  105. // Build map name
  106. char mapname[ 256 ];
  107. Q_StripExtension( pFileName, mapname, sizeof(mapname) );
  108. char *pszFileName = (char*)Q_UnqualifiedFileName(mapname);
  109. int nLen = (int)( (size_t)pGame - (size_t)pFileName ) + 1;
  110. Q_strncpy( m_pVMFFileName, pFileName, nLen );
  111. Q_strncat( m_pVMFFileName, "\\content\\", sizeof(m_pVMFFileName) );
  112. Q_strncat( m_pVMFFileName, pGame + 6, sizeof(m_pVMFFileName) );
  113. Q_SetExtension( m_pVMFFileName, ".vmf", sizeof(m_pVMFFileName) );
  114. // Make sure new entities start with ids at 0
  115. CDmeVMFEntity::SetNextEntityId( 0 );
  116. // Build the Edit file name
  117. Q_StripExtension( m_pVMFFileName, m_pEditFileName, sizeof(m_pEditFileName) );
  118. Q_strncat( m_pEditFileName, ".vle", sizeof( m_pEditFileName ) );
  119. // Store the BSP file name
  120. Q_strncpy( m_pBSPFileName, pFileName, sizeof( m_pBSPFileName ) );
  121. // Set the txt file name.
  122. // If we loaded a .bsp, clear out what we're doing
  123. // load the Edits file into memory, assign it as our "root"
  124. CDmElement *pEdit = NULL;
  125. if ( !V_stricmp( Q_GetFileExtension( pFileName ), "vle" ) )
  126. {
  127. if ( g_pDataModel->RestoreFromFile( m_pEditFileName, NULL, "vmf", &pEdit ) != DMFILEID_INVALID )
  128. {
  129. // If we successfully read the file in, ask it for the max hammer id
  130. //int nMaxHammerId = pVMF->GetAttributeValue<int>( "maxHammerId" );
  131. //CDmeVMFEntity::SetNextEntityId( nMaxHammerId + 1 );
  132. m_hEditRoot = pEdit;
  133. SetDirty( false );
  134. }
  135. }
  136. if (pEdit == NULL)
  137. {
  138. if ( g_pFileSystem->FileExists( m_pEditFileName ) )
  139. {
  140. char pBuf[1024];
  141. Q_snprintf( pBuf, sizeof(pBuf), "File %s already exists!\n", m_pEditFileName );
  142. m_pEditFileName[0] = 0;
  143. vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Unable to overwrite file!\n", pBuf, g_pVcdBlockTool );
  144. pMessageBox->DoModal( );
  145. return false;
  146. }
  147. DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( m_pEditFileName );
  148. m_hEditRoot = CreateElement<CDmElement>( "root", fileid );
  149. m_hEditRoot->AddAttribute( "entities", AT_ELEMENT_ARRAY );
  150. g_pDataModel->SetFileRoot( fileid, m_hEditRoot );
  151. SetDirty( true );
  152. }
  153. guard.Release();
  154. // tell the engine to actually load the map
  155. char cmd[ 256 ];
  156. Q_snprintf( cmd, sizeof( cmd ), "disconnect; map %s\n", pszFileName );
  157. enginetools->Command( cmd );
  158. enginetools->Execute( );
  159. return true;
  160. }
  161. void CVcdBlockDoc::SaveToFile( )
  162. {
  163. if ( m_hEditRoot.Get() && m_pEditFileName && m_pEditFileName[0] )
  164. {
  165. g_pDataModel->SaveToFile( m_pEditFileName, NULL, "keyvalues", "vmf", m_hEditRoot );
  166. }
  167. SetDirty( false );
  168. }
  169. //-----------------------------------------------------------------------------
  170. // Returns the root object
  171. //-----------------------------------------------------------------------------
  172. CDmElement *CVcdBlockDoc::GetRootObject()
  173. {
  174. return m_hEditRoot;
  175. }
  176. //-----------------------------------------------------------------------------
  177. // Returns the entity list
  178. //-----------------------------------------------------------------------------
  179. CDmAttribute *CVcdBlockDoc::GetEntityList()
  180. {
  181. return m_hEditRoot ? m_hEditRoot->GetAttribute( "entities", AT_ELEMENT_ARRAY ) : NULL;
  182. }
  183. //-----------------------------------------------------------------------------
  184. // Purpose:
  185. //-----------------------------------------------------------------------------
  186. void CVcdBlockDoc::AddNewInfoTarget( const Vector &vecOrigin, const QAngle &angAngles )
  187. {
  188. CDmrElementArray<> entities = GetEntityList();
  189. CDmeVMFEntity *pTarget;
  190. {
  191. CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Add Info Target", "Add Info Target" );
  192. pTarget = CreateElement<CDmeVMFEntity>( "", entities.GetOwner()->GetFileId() );
  193. pTarget->SetValue( "classname", "info_target" );
  194. pTarget->SetRenderOrigin( vecOrigin );
  195. pTarget->SetRenderAngles( angAngles );
  196. entities.AddToTail( pTarget );
  197. pTarget->MarkDirty();
  198. pTarget->DrawInEngine( true );
  199. }
  200. g_pVcdBlockTool->GetInfoTargetBrowser()->SelectNode( pTarget );
  201. }
  202. //-----------------------------------------------------------------------------
  203. // Purpose:
  204. //-----------------------------------------------------------------------------
  205. void CVcdBlockDoc::AddNewInfoTarget( void )
  206. {
  207. Vector vecOrigin;
  208. QAngle angAngles;
  209. float flFov;
  210. clienttools->GetLocalPlayerEyePosition( vecOrigin, angAngles, flFov );
  211. AddNewInfoTarget( vecOrigin, vec3_angle );
  212. }
  213. //-----------------------------------------------------------------------------
  214. // Deletes a commentary node
  215. //-----------------------------------------------------------------------------
  216. void CVcdBlockDoc::DeleteInfoTarget( CDmeVMFEntity *pNode )
  217. {
  218. CDmrElementArray<CDmElement> entities = GetEntityList();
  219. int nCount = entities.Count();
  220. for ( int i = 0; i < nCount; ++i )
  221. {
  222. if ( pNode == entities[i] )
  223. {
  224. CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Delete Info Target", "Delete Info Target" );
  225. CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] );
  226. pNode->DrawInEngine( false );
  227. entities.FastRemove( i );
  228. return;
  229. }
  230. }
  231. }
  232. //-----------------------------------------------------------------------------
  233. // Purpose:
  234. // Input : &vecOrigin -
  235. // &angAbsAngles -
  236. // Output : CDmeVMFEntity
  237. //-----------------------------------------------------------------------------
  238. CDmeVMFEntity *CVcdBlockDoc::GetInfoTargetForLocation( Vector &vecOrigin, QAngle &angAbsAngles )
  239. {
  240. const CDmrElementArray<> entities = GetEntityList();
  241. int nCount = entities.Count();
  242. for ( int i = 0; i < nCount; ++i )
  243. {
  244. CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] );
  245. Vector &vecAngles = *(Vector*)(&pNode->GetRenderAngles());
  246. if ( pNode->GetRenderOrigin().DistTo( vecOrigin ) < 1e-3 && vecAngles.DistTo( *(Vector*)&angAbsAngles ) < 1e-1 )
  247. return pNode;
  248. }
  249. return NULL;
  250. }
  251. //-----------------------------------------------------------------------------
  252. // Purpose:
  253. // Input : &vecStart -
  254. // &vecEnd -
  255. // Output : CDmeVMFEntity
  256. //-----------------------------------------------------------------------------
  257. CDmeVMFEntity *CVcdBlockDoc::GetInfoTargetForLocation( Vector &vecStart, Vector &vecEnd )
  258. {
  259. Vector vecDelta;
  260. float flEndDist;
  261. vecDelta = vecEnd - vecStart;
  262. flEndDist = VectorNormalize( vecDelta );
  263. CDmeVMFEntity *pSelectedNode = NULL;
  264. float flMinDistFromLine = 1E30;
  265. const CDmrElementArray<CDmElement> entities = GetEntityList();
  266. int nCount = entities.Count();
  267. for ( int i = 0; i < nCount; ++i )
  268. {
  269. CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] );
  270. float flDistAway = DotProduct( pNode->GetRenderOrigin() - vecStart, vecDelta );
  271. if (flDistAway > 0.0 && flDistAway < flEndDist)
  272. {
  273. float flDistFromLine = (pNode->GetRenderOrigin() - vecStart - vecDelta * flDistAway).Length();
  274. if (flDistFromLine < flMinDistFromLine)
  275. {
  276. pSelectedNode = pNode;
  277. flMinDistFromLine = flDistFromLine;
  278. }
  279. }
  280. }
  281. return pSelectedNode;
  282. }
  283. //-----------------------------------------------------------------------------
  284. // Populate string choice lists
  285. //-----------------------------------------------------------------------------
  286. bool CVcdBlockDoc::GetStringChoiceList( const char *pChoiceListType, CDmElement *pElement,
  287. const char *pAttributeName, bool bArrayElement, StringChoiceList_t &list )
  288. {
  289. if ( !Q_stricmp( pChoiceListType, "info_targets" ) )
  290. {
  291. const CDmrElementArray<> entities = GetEntityList();
  292. StringChoice_t sChoice;
  293. sChoice.m_pValue = "";
  294. sChoice.m_pChoiceString = "";
  295. list.AddToTail( sChoice );
  296. int nCount = entities.Count();
  297. for ( int i = 0; i < nCount; ++i )
  298. {
  299. CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] );
  300. if ( !V_stricmp( pNode->GetClassName(), "info_target" ) )
  301. {
  302. StringChoice_t sChoice;
  303. sChoice.m_pValue = pNode->GetTargetName();
  304. sChoice.m_pChoiceString = pNode->GetTargetName();
  305. list.AddToTail( sChoice );
  306. }
  307. }
  308. return true;
  309. }
  310. return false;
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Populate element choice lists
  314. //-----------------------------------------------------------------------------
  315. bool CVcdBlockDoc::GetElementChoiceList( const char *pChoiceListType, CDmElement *pElement,
  316. const char *pAttributeName, bool bArrayElement, ElementChoiceList_t &list )
  317. {
  318. if ( !Q_stricmp( pChoiceListType, "allelements" ) )
  319. {
  320. AddElementsRecursively( m_hEditRoot, list );
  321. return true;
  322. }
  323. if ( !Q_stricmp( pChoiceListType, "info_targets" ) )
  324. {
  325. const CDmrElementArray<> entities = GetEntityList();
  326. bool bFound = false;
  327. int nCount = entities.Count();
  328. for ( int i = 0; i < nCount; ++i )
  329. {
  330. CDmeVMFEntity *pNode = CastElement< CDmeVMFEntity >( entities[ i ] );
  331. if ( !V_stricmp( pNode->GetClassName(), "info_target" ) )
  332. {
  333. bFound = true;
  334. ElementChoice_t sChoice;
  335. sChoice.m_pValue = pNode;
  336. sChoice.m_pChoiceString = pNode->GetTargetName();
  337. list.AddToTail( sChoice );
  338. }
  339. }
  340. return bFound;
  341. }
  342. // by default, try to treat the choice list type as a Dme element type
  343. AddElementsRecursively( m_hEditRoot, list, pChoiceListType );
  344. return list.Count() > 0;
  345. }
  346. //-----------------------------------------------------------------------------
  347. // Called when data changes
  348. //-----------------------------------------------------------------------------
  349. void CVcdBlockDoc::OnDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags )
  350. {
  351. SetDirty( nNotifyFlags & NOTIFY_SETDIRTYFLAG ? true : false );
  352. m_pCallback->OnDocChanged( pReason, nNotifySource, nNotifyFlags );
  353. }
  354. //-----------------------------------------------------------------------------
  355. // List of all entity classnames to copy over from the original block
  356. //-----------------------------------------------------------------------------
  357. static const char *s_pUseOriginalClasses[] =
  358. {
  359. "worldspawn",
  360. "func_occluder",
  361. NULL
  362. };
  363. //-----------------------------------------------------------------------------
  364. // The server just loaded, populate the list with the entities is has
  365. //-----------------------------------------------------------------------------
  366. void CVcdBlockDoc::ServerLevelInitPostEntity( void )
  367. {
  368. CDmrElementArray<> entityList = GetEntityList();
  369. if ( entityList.Count() )
  370. {
  371. VerifyAllEdits( entityList );
  372. }
  373. else
  374. {
  375. InitializeFromServer( entityList );
  376. }
  377. }
  378. //-----------------------------------------------------------------------------
  379. // Create a list of entities based on what the server has
  380. //-----------------------------------------------------------------------------
  381. void CVcdBlockDoc::InitializeFromServer( CDmrElementArray<> &entityList )
  382. {
  383. CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Initialize From Server" );
  384. entityList.RemoveAll();
  385. // initialize list with entities on the server
  386. CBaseEntity *pServerEnt = servertools->FirstEntity();
  387. while (pServerEnt)
  388. {
  389. char classname[256];
  390. if (servertools->GetKeyValue( pServerEnt, "classname", classname, sizeof( classname ) ) )
  391. {
  392. if ( !Q_stricmp( classname, "info_target" ))
  393. {
  394. char hammerid[256];
  395. if ( servertools->GetKeyValue( pServerEnt, "hammerid", hammerid, sizeof( hammerid ) ) )
  396. {
  397. int nextId = CDmeVMFEntity::GetNextEntityId();
  398. CDmeVMFEntity::SetNextEntityId( atoi( hammerid ) );
  399. CDmeVMFEntity *pTarget = CreateElement<CDmeVMFEntity>( "", entityList.GetOwner()->GetFileId() );
  400. CDmeVMFEntity::SetNextEntityId( nextId );
  401. if ( pTarget->CopyFromServer( pServerEnt ) )
  402. {
  403. entityList.AddToTail( pTarget );
  404. }
  405. }
  406. }
  407. }
  408. pServerEnt = servertools->NextEntity( pServerEnt );
  409. }
  410. }
  411. //-----------------------------------------------------------------------------
  412. // Check the list of entities on the server against the edits that are already made
  413. //-----------------------------------------------------------------------------
  414. void CVcdBlockDoc::VerifyAllEdits( const CDmrElementArray<> &entityList )
  415. {
  416. // already filled in
  417. for (int i = 0; i < entityList.Count(); i++)
  418. {
  419. CDmeVMFEntity *pEntity = CastElement<CDmeVMFEntity>( entityList[i] );
  420. CBaseEntity *pServerEntity = servertools->FindEntityByHammerID( pEntity->GetEntityId() );
  421. if (pServerEntity != NULL)
  422. {
  423. if (!pEntity->IsSameOnServer( pServerEntity ))
  424. {
  425. pEntity->MarkDirty();
  426. }
  427. else
  428. {
  429. pEntity->MarkDirty(false);
  430. }
  431. }
  432. else
  433. {
  434. pEntity->CreateOnServer();
  435. pEntity->MarkDirty();
  436. }
  437. }
  438. }
  439. //-----------------------------------------------------------------------------
  440. // Load the VMF file, merge in all the edits, write it back out
  441. //-----------------------------------------------------------------------------
  442. bool CVcdBlockDoc::CopyEditsToVMF( )
  443. {
  444. const CDmrElementArray<CDmElement> entityList = GetEntityList();
  445. CDmElement *pVMF = NULL;
  446. DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( m_pVMFFileName );
  447. if ( g_pDataModel->RestoreFromFile( m_pVMFFileName, NULL, "vmf", &pVMF ) == DMFILEID_INVALID )
  448. {
  449. // needs some kind of error message
  450. return false;
  451. }
  452. CDmrElementArray<CDmElement> vmfEntities( pVMF, "entities" );
  453. int nVMFCount = vmfEntities.Count();
  454. for (int i = 0; i < nVMFCount; i++)
  455. {
  456. CDmElement *pVMFEntity = vmfEntities[i];
  457. char classname[256];
  458. pVMFEntity->GetValueAsString( "classname", classname, sizeof( classname ) );
  459. if ( Q_stricmp( "info_target", classname ) )
  460. continue;
  461. int nHammerID = atoi( pVMFEntity->GetName() );
  462. // find a match.
  463. int nCount = entityList.Count();
  464. for (int j = 0; j < nCount; j++)
  465. {
  466. CDmeVMFEntity *pEntity = CastElement<CDmeVMFEntity>( entityList[j] );
  467. if ( pEntity->IsDirty() && pEntity->GetEntityId() == nHammerID)
  468. {
  469. char text[256];
  470. pEntity->GetValueAsString( "targetname", text, sizeof( text ) );
  471. pVMFEntity->SetValueFromString( "targetname", text );
  472. pEntity->GetValueAsString( "origin", text, sizeof( text ) );
  473. pVMFEntity->SetValueFromString( "origin", text );
  474. pEntity->GetValueAsString( "angles", text, sizeof( text ) );
  475. pVMFEntity->SetValueFromString( "angles", text );
  476. pEntity->MarkDirty(false);
  477. }
  478. }
  479. }
  480. // add the new entities
  481. int nCount = entityList.Count();
  482. for (int j = 0; j < nCount; j++)
  483. {
  484. CDmeVMFEntity *pEntity = CastElement<CDmeVMFEntity>( entityList[j] );
  485. if ( pEntity->IsDirty())
  486. {
  487. CDmElement *pVMFEntity = CreateElement<CDmElement>( pEntity->GetName(), fileid );
  488. char text[256];
  489. pEntity->GetValueAsString( "classname", text, sizeof( text ) );
  490. pVMFEntity->SetValue( "classname", text );
  491. pEntity->GetValueAsString( "targetname", text, sizeof( text ) );
  492. pVMFEntity->SetValue( "targetname", text );
  493. pEntity->GetValueAsString( "origin", text, sizeof( text ) );
  494. pVMFEntity->SetValue( "origin", text );
  495. pEntity->GetValueAsString( "angles", text, sizeof( text ) );
  496. pVMFEntity->SetValue( "angles", text );
  497. vmfEntities.AddToTail( pVMFEntity );
  498. pEntity->MarkDirty(false);
  499. }
  500. }
  501. // currently, don't overwrite the vmf, not sure if this is serializing correctly yet
  502. char tmpname[ 256 ];
  503. Q_StripExtension( m_pVMFFileName, tmpname, sizeof(tmpname) );
  504. Q_SetExtension( tmpname, ".vme", sizeof(tmpname) );
  505. if (!g_pDataModel->SaveToFile( tmpname, NULL, "keyvalues", "vmf", pVMF ))
  506. {
  507. // needs some kind of error message
  508. return false;
  509. }
  510. /*
  511. // If we successfully read the file in, ask it for the max hammer id
  512. int nMaxHammerId = pVMF->GetAttributeValue<int>( "maxHammerId" );
  513. CDmeVMFEntity::SetNextEntityId( nMaxHammerId + 1 );
  514. m_hVMFRoot = pVMF;
  515. */
  516. return true;
  517. }
  518. bool CVcdBlockDoc::RememberPlayerPosition()
  519. {
  520. return true;
  521. }