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.

1364 lines
35 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // nav_file.cpp
  9. // Reading and writing nav files
  10. // Author: Michael S. Booth ([email protected]), January-September 2003
  11. #include "cbase.h"
  12. #include "nav_mesh.h"
  13. #ifdef CSTRIKE_DLL
  14. #include "cs_shareddefs.h"
  15. #include "cs_nav_pathfind.h"
  16. #endif
  17. #if 0
  18. //--------------------------------------------------------------------------------------------------------------
  19. /// The current version of the nav file format
  20. const int NavCurrentVersion = 9;
  21. //--------------------------------------------------------------------------------------------------------------
  22. //
  23. // The 'place directory' is used to save and load places from
  24. // nav files in a size-efficient manner that also allows for the
  25. // order of the place ID's to change without invalidating the
  26. // nav files.
  27. //
  28. // The place directory is stored in the nav file as a list of
  29. // place name strings. Each nav area then contains an index
  30. // into that directory, or zero if no place has been assigned to
  31. // that area.
  32. //
  33. class PlaceDirectory
  34. {
  35. public:
  36. typedef unsigned short IndexType;
  37. void Reset( void )
  38. {
  39. m_directory.RemoveAll();
  40. }
  41. /// return true if this place is already in the directory
  42. bool IsKnown( Place place ) const
  43. {
  44. return m_directory.HasElement( place );
  45. }
  46. /// return the directory index corresponding to this Place (0 = no entry)
  47. IndexType GetIndex( Place place ) const
  48. {
  49. if (place == UNDEFINED_PLACE)
  50. return 0;
  51. int i = m_directory.Find( place );
  52. if (i < 0)
  53. {
  54. Assert( false && "PlaceDirectory::GetIndex failure" );
  55. return 0;
  56. }
  57. return (IndexType)(i+1);
  58. }
  59. /// add the place to the directory if not already known
  60. void AddPlace( Place place )
  61. {
  62. if (place == UNDEFINED_PLACE)
  63. return;
  64. Assert( place < 1000 );
  65. if (IsKnown( place ))
  66. return;
  67. m_directory.AddToTail( place );
  68. }
  69. /// given an index, return the Place
  70. Place IndexToPlace( IndexType entry ) const
  71. {
  72. if (entry == 0)
  73. return UNDEFINED_PLACE;
  74. int i = entry-1;
  75. if (i >= m_directory.Count())
  76. {
  77. Assert( false && "PlaceDirectory::IndexToPlace: Invalid entry" );
  78. return UNDEFINED_PLACE;
  79. }
  80. return m_directory[ i ];
  81. }
  82. /// store the directory
  83. void Save( FileHandle_t file )
  84. {
  85. // store number of entries in directory
  86. IndexType count = (IndexType)m_directory.Count();
  87. filesystem->Write( &count, sizeof(IndexType), file );
  88. // store entries
  89. for( int i=0; i<m_directory.Count(); ++i )
  90. {
  91. const char *placeName = TheNavMesh->PlaceToName( m_directory[i] );
  92. // store string length followed by string itself
  93. unsigned short len = (unsigned short)(strlen( placeName ) + 1);
  94. filesystem->Write( &len, sizeof(unsigned short), file );
  95. filesystem->Write( placeName, len, file );
  96. }
  97. }
  98. /// load the directory
  99. void Load( FileHandle_t file )
  100. {
  101. // read number of entries
  102. IndexType count;
  103. filesystem->Read( &count, sizeof(IndexType), file );
  104. m_directory.RemoveAll();
  105. // read each entry
  106. char placeName[256];
  107. unsigned short len;
  108. for( int i=0; i<count; ++i )
  109. {
  110. filesystem->Read( &len, sizeof(unsigned short), file );
  111. filesystem->Read( placeName, len, file );
  112. AddPlace( TheNavMesh->NameToPlace( placeName ) );
  113. }
  114. }
  115. private:
  116. CUtlVector< Place > m_directory;
  117. };
  118. static PlaceDirectory placeDirectory;
  119. //--------------------------------------------------------------------------------------------------------------
  120. /**
  121. * Replace extension with "bsp"
  122. */
  123. char *GetBspFilename( const char *navFilename )
  124. {
  125. static char bspFilename[256];
  126. Q_snprintf( bspFilename, sizeof( bspFilename ), "maps\\%s.bsp", STRING( gpGlobals->mapname ) );
  127. int len = strlen( bspFilename );
  128. if (len < 3)
  129. return NULL;
  130. bspFilename[ len-3 ] = 'b';
  131. bspFilename[ len-2 ] = 's';
  132. bspFilename[ len-1 ] = 'p';
  133. return bspFilename;
  134. }
  135. //--------------------------------------------------------------------------------------------------------------
  136. /*
  137. void CNavArea::Save( FILE *fp ) const
  138. {
  139. fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.lo.y, m_extent.lo.z );
  140. fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.lo.y, m_neZ );
  141. fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.hi.y, m_extent.hi.z );
  142. fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.hi.y, m_swZ );
  143. static int base = 1;
  144. fprintf( fp, "\n\ng %04dArea%s%s%s%s\n", m_id,
  145. (GetAttributes() & BOT_NAV_CROUCH) ? "CROUCH" : "",
  146. (GetAttributes() & BOT_NAV_JUMP) ? "JUMP" : "",
  147. (GetAttributes() & BOT_NAV_PRECISE) ? "PRECISE" : "",
  148. (GetAttributes() & BOT_NAV_NO_JUMP) ? "NO_JUMP" : "" );
  149. fprintf( fp, "f %d %d %d %d\n\n", base, base+1, base+2, base+3 );
  150. base += 4;
  151. }
  152. */
  153. //--------------------------------------------------------------------------------------------------------------
  154. /**
  155. * Save a navigation area to the opened binary stream
  156. */
  157. void CNavArea::Save( FileHandle_t file, unsigned int version ) const
  158. {
  159. // save ID
  160. filesystem->Write( &m_id, sizeof(unsigned int), file );
  161. // save attribute flags
  162. filesystem->Write( &m_attributeFlags, sizeof(unsigned short), file );
  163. // save extent of area
  164. filesystem->Write( &m_extent, 6*sizeof(float), file );
  165. // save heights of implicit corners
  166. filesystem->Write( &m_neZ, sizeof(float), file );
  167. filesystem->Write( &m_swZ, sizeof(float), file );
  168. // save connections to adjacent areas
  169. // in the enum order NORTH, EAST, SOUTH, WEST
  170. for( int d=0; d<NUM_DIRECTIONS; d++ )
  171. {
  172. // save number of connections for this direction
  173. unsigned int count = m_connect[d].Count();
  174. filesystem->Write( &count, sizeof(unsigned int), file );
  175. FOR_EACH_LL( m_connect[d], it )
  176. {
  177. NavConnect connect = m_connect[d][ it ];
  178. filesystem->Write( &connect.area->m_id, sizeof(unsigned int), file );
  179. }
  180. }
  181. //
  182. // Store hiding spots for this area
  183. //
  184. unsigned char count;
  185. if (m_hidingSpotList.Count() > 255)
  186. {
  187. count = 255;
  188. Warning( "Warning: NavArea #%d: Truncated hiding spot list to 255\n", m_id );
  189. }
  190. else
  191. {
  192. count = (unsigned char)m_hidingSpotList.Count();
  193. }
  194. filesystem->Write( &count, sizeof(unsigned char), file );
  195. // store HidingSpot objects
  196. unsigned int saveCount = 0;
  197. FOR_EACH_LL( m_hidingSpotList, hit )
  198. {
  199. HidingSpot *spot = m_hidingSpotList[ hit ];
  200. spot->Save( file, version );
  201. // overflow check
  202. if (++saveCount == count)
  203. break;
  204. }
  205. //
  206. // Save the approach areas for this area
  207. //
  208. // save number of approach areas
  209. filesystem->Write( &m_approachCount, sizeof(unsigned char), file );
  210. // save approach area info
  211. unsigned char type;
  212. unsigned int zero = 0;
  213. for( int a=0; a<m_approachCount; ++a )
  214. {
  215. if (m_approach[a].here.area)
  216. filesystem->Write( &m_approach[a].here.area->m_id, sizeof(unsigned int), file );
  217. else
  218. filesystem->Write( &zero, sizeof(unsigned int), file );
  219. if (m_approach[a].prev.area)
  220. filesystem->Write( &m_approach[a].prev.area->m_id, sizeof(unsigned int), file );
  221. else
  222. filesystem->Write( &zero, sizeof(unsigned int), file );
  223. type = (unsigned char)m_approach[a].prevToHereHow;
  224. filesystem->Write( &type, sizeof(unsigned char), file );
  225. if (m_approach[a].next.area)
  226. filesystem->Write( &m_approach[a].next.area->m_id, sizeof(unsigned int), file );
  227. else
  228. filesystem->Write( &zero, sizeof(unsigned int), file );
  229. type = (unsigned char)m_approach[a].hereToNextHow;
  230. filesystem->Write( &type, sizeof(unsigned char), file );
  231. }
  232. //
  233. // Save encounter spots for this area
  234. //
  235. {
  236. // save number of encounter paths for this area
  237. unsigned int count = m_spotEncounterList.Count();
  238. filesystem->Write( &count, sizeof(unsigned int), file );
  239. SpotEncounter *e;
  240. FOR_EACH_LL( m_spotEncounterList, it )
  241. {
  242. e = m_spotEncounterList[ it ];
  243. if (e->from.area)
  244. filesystem->Write( &e->from.area->m_id, sizeof(unsigned int), file );
  245. else
  246. filesystem->Write( &zero, sizeof(unsigned int), file );
  247. unsigned char dir = (unsigned char)e->fromDir;
  248. filesystem->Write( &dir, sizeof(unsigned char), file );
  249. if (e->to.area)
  250. filesystem->Write( &e->to.area->m_id, sizeof(unsigned int), file );
  251. else
  252. filesystem->Write( &zero, sizeof(unsigned int), file );
  253. dir = (unsigned char)e->toDir;
  254. filesystem->Write( &dir, sizeof(unsigned char), file );
  255. // write list of spots along this path
  256. unsigned char spotCount;
  257. if (e->spotList.Count() > 255)
  258. {
  259. spotCount = 255;
  260. Warning( "Warning: NavArea #%d: Truncated encounter spot list to 255\n", m_id );
  261. }
  262. else
  263. {
  264. spotCount = (unsigned char)e->spotList.Count();
  265. }
  266. filesystem->Write( &spotCount, sizeof(unsigned char), file );
  267. saveCount = 0;
  268. FOR_EACH_LL( e->spotList, sit )
  269. {
  270. SpotOrder *order = &e->spotList[ sit ];
  271. // order->spot may be NULL if we've loaded a nav mesh that has been edited but not re-analyzed
  272. unsigned int id = (order->spot) ? order->spot->GetID() : 0;
  273. filesystem->Write( &id, sizeof(unsigned int), file );
  274. unsigned char t = (unsigned char)(255 * order->t);
  275. filesystem->Write( &t, sizeof(unsigned char), file );
  276. // overflow check
  277. if (++saveCount == spotCount)
  278. break;
  279. }
  280. }
  281. }
  282. // store place dictionary entry
  283. PlaceDirectory::IndexType entry = placeDirectory.GetIndex( GetPlace() );
  284. filesystem->Write( &entry, sizeof(entry), file );
  285. // write out ladder info
  286. int i;
  287. for ( i=0; i<CSNavLadder::NUM_LADDER_DIRECTIONS; ++i )
  288. {
  289. // save number of encounter paths for this area
  290. unsigned int count = m_ladder[i].Count();
  291. filesystem->Write( &count, sizeof(unsigned int), file );
  292. NavLadderConnect ladder;
  293. FOR_EACH_LL( m_ladder[i], it )
  294. {
  295. ladder = m_ladder[i][it];
  296. unsigned int id = ladder.ladder->GetID();
  297. filesystem->Write( &id, sizeof( id ), file );
  298. }
  299. }
  300. // save earliest occupy times
  301. for( i=0; i<MAX_NAV_TEAMS; ++i )
  302. {
  303. // no spot in the map should take longer than this to reach
  304. filesystem->Write( &m_earliestOccupyTime[i], sizeof(m_earliestOccupyTime[i]), file );
  305. }
  306. }
  307. //--------------------------------------------------------------------------------------------------------------
  308. /**
  309. * Load a navigation area from the file
  310. */
  311. void CNavArea::Load( FileHandle_t file, unsigned int version )
  312. {
  313. // load ID
  314. filesystem->Read( &m_id, sizeof(unsigned int), file );
  315. // update nextID to avoid collisions
  316. if (m_id >= m_nextID)
  317. m_nextID = m_id+1;
  318. // load attribute flags
  319. if ( version <= 8 )
  320. {
  321. unsigned char flags = 0;
  322. filesystem->Read( &flags, sizeof(unsigned char), file );
  323. m_attributeFlags = flags;
  324. }
  325. else
  326. {
  327. filesystem->Read( &m_attributeFlags, sizeof(unsigned short), file );
  328. }
  329. // load extent of area
  330. filesystem->Read( &m_extent, 6*sizeof(float), file );
  331. m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f;
  332. m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f;
  333. m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f;
  334. // load heights of implicit corners
  335. filesystem->Read( &m_neZ, sizeof(float), file );
  336. filesystem->Read( &m_swZ, sizeof(float), file );
  337. CheckWaterLevel();
  338. // load connections (IDs) to adjacent areas
  339. // in the enum order NORTH, EAST, SOUTH, WEST
  340. for( int d=0; d<NUM_DIRECTIONS; d++ )
  341. {
  342. // load number of connections for this direction
  343. unsigned int count;
  344. int result = filesystem->Read( &count, sizeof(unsigned int), file );
  345. Assert( result == sizeof(unsigned int) );
  346. for( unsigned int i=0; i<count; ++i )
  347. {
  348. NavConnect connect;
  349. result = filesystem->Read( &connect.id, sizeof(unsigned int), file );
  350. Assert( result == sizeof(unsigned int) );
  351. // don't allow self-referential connections
  352. if ( connect.id != m_id )
  353. {
  354. m_connect[d].AddToTail( connect );
  355. }
  356. }
  357. }
  358. //
  359. // Load hiding spots
  360. //
  361. // load number of hiding spots
  362. unsigned char hidingSpotCount;
  363. filesystem->Read( &hidingSpotCount, sizeof(unsigned char), file );
  364. if (version == 1)
  365. {
  366. // load simple vector array
  367. Vector pos;
  368. for( int h=0; h<hidingSpotCount; ++h )
  369. {
  370. filesystem->Read( &pos, 3 * sizeof(float), file );
  371. // create new hiding spot and put on master list
  372. HidingSpot *spot = TheNavMesh->CreateHidingSpot();
  373. spot->SetPosition( pos );
  374. spot->SetFlags( HidingSpot::IN_COVER );
  375. m_hidingSpotList.AddToTail( spot );
  376. }
  377. }
  378. else
  379. {
  380. // load HidingSpot objects for this area
  381. for( int h=0; h<hidingSpotCount; ++h )
  382. {
  383. // create new hiding spot and put on master list
  384. HidingSpot *spot = TheNavMesh->CreateHidingSpot();
  385. spot->Load( file, version );
  386. m_hidingSpotList.AddToTail( spot );
  387. }
  388. }
  389. //
  390. // Load number of approach areas
  391. //
  392. filesystem->Read( &m_approachCount, sizeof(unsigned char), file );
  393. // load approach area info (IDs)
  394. unsigned char type;
  395. for( int a=0; a<m_approachCount; ++a )
  396. {
  397. filesystem->Read( &m_approach[a].here.id, sizeof(unsigned int), file );
  398. filesystem->Read( &m_approach[a].prev.id, sizeof(unsigned int), file );
  399. filesystem->Read( &type, sizeof(unsigned char), file );
  400. m_approach[a].prevToHereHow = (NavTraverseType)type;
  401. filesystem->Read( &m_approach[a].next.id, sizeof(unsigned int), file );
  402. filesystem->Read( &type, sizeof(unsigned char), file );
  403. m_approach[a].hereToNextHow = (NavTraverseType)type;
  404. }
  405. //
  406. // Load encounter paths for this area
  407. //
  408. unsigned int count;
  409. filesystem->Read( &count, sizeof(unsigned int), file );
  410. if (version < 3)
  411. {
  412. // old data, read and discard
  413. for( unsigned int e=0; e<count; ++e )
  414. {
  415. SpotEncounter encounter;
  416. filesystem->Read( &encounter.from.id, sizeof(unsigned int), file );
  417. filesystem->Read( &encounter.to.id, sizeof(unsigned int), file );
  418. filesystem->Read( &encounter.path.from.x, 3 * sizeof(float), file );
  419. filesystem->Read( &encounter.path.to.x, 3 * sizeof(float), file );
  420. // read list of spots along this path
  421. unsigned char spotCount;
  422. filesystem->Read( &spotCount, sizeof(unsigned char), file );
  423. for( int s=0; s<spotCount; ++s )
  424. {
  425. Vector pos;
  426. filesystem->Read( &pos, 3*sizeof(float), file );
  427. filesystem->Read( &pos, sizeof(float), file );
  428. }
  429. }
  430. return;
  431. }
  432. for( unsigned int e=0; e<count; ++e )
  433. {
  434. SpotEncounter *encounter = new SpotEncounter;
  435. filesystem->Read( &encounter->from.id, sizeof(unsigned int), file );
  436. unsigned char dir;
  437. filesystem->Read( &dir, sizeof(unsigned char), file );
  438. encounter->fromDir = static_cast<NavDirType>( dir );
  439. filesystem->Read( &encounter->to.id, sizeof(unsigned int), file );
  440. filesystem->Read( &dir, sizeof(unsigned char), file );
  441. encounter->toDir = static_cast<NavDirType>( dir );
  442. // read list of spots along this path
  443. unsigned char spotCount;
  444. filesystem->Read( &spotCount, sizeof(unsigned char), file );
  445. SpotOrder order;
  446. for( int s=0; s<spotCount; ++s )
  447. {
  448. filesystem->Read( &order.id, sizeof(unsigned int), file );
  449. unsigned char t;
  450. filesystem->Read( &t, sizeof(unsigned char), file );
  451. order.t = (float)t/255.0f;
  452. encounter->spotList.AddToTail( order );
  453. }
  454. m_spotEncounterList.AddToTail( encounter );
  455. }
  456. if (version < 5)
  457. return;
  458. //
  459. // Load Place data
  460. //
  461. PlaceDirectory::IndexType entry;
  462. filesystem->Read( &entry, sizeof(entry), file );
  463. // convert entry to actual Place
  464. SetPlace( placeDirectory.IndexToPlace( entry ) );
  465. if ( version < 7 )
  466. return;
  467. // load ladder data
  468. for ( int dir=0; dir<CSNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
  469. {
  470. filesystem->Read( &count, sizeof(unsigned int), file );
  471. {
  472. for( unsigned int i=0; i<count; ++i )
  473. {
  474. NavLadderConnect connect;
  475. filesystem->Read( &connect.id, sizeof(unsigned int), file );
  476. bool alreadyConnected = false;
  477. FOR_EACH_LL( m_ladder[dir], j )
  478. {
  479. if ( m_ladder[dir][j].id == connect.id )
  480. {
  481. alreadyConnected = true;
  482. break;
  483. }
  484. }
  485. if ( !alreadyConnected )
  486. {
  487. m_ladder[dir].AddToTail( connect );
  488. }
  489. }
  490. }
  491. }
  492. if ( version < 8 )
  493. return;
  494. // load earliest occupy times
  495. for( int i=0; i<MAX_NAV_TEAMS; ++i )
  496. {
  497. // no spot in the map should take longer than this to reach
  498. filesystem->Read( &m_earliestOccupyTime[i], sizeof(m_earliestOccupyTime[i]), file );
  499. }
  500. }
  501. //--------------------------------------------------------------------------------------------------------------
  502. /**
  503. * Convert loaded IDs to pointers
  504. * Make sure all IDs are converted, even if corrupt data is encountered.
  505. */
  506. NavErrorType CNavArea::PostLoad( void )
  507. {
  508. NavErrorType error = NAV_OK;
  509. for ( int dir=0; dir < CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
  510. {
  511. FOR_EACH_VEC( m_ladder[dir], it )
  512. {
  513. NavLadderConnect& connect = m_ladder[dir][it];
  514. unsigned int id = connect.id;
  515. if ( TheNavMesh->GetLadders().Find( connect.ladder ) == TheNavMesh->GetLadders().InvalidIndex() )
  516. {
  517. connect.ladder = TheNavMesh->GetLadderByID( id );
  518. }
  519. if (id && connect.ladder == NULL)
  520. {
  521. Msg( "CNavArea::PostLoad: Corrupt navigation ladder data. Cannot connect Navigation Areas.\n" );
  522. error = NAV_CORRUPT_DATA;
  523. }
  524. }
  525. }
  526. // connect areas together
  527. for( int d=0; d<NUM_DIRECTIONS; d++ )
  528. {
  529. FOR_EACH_VEC( m_connect[d], it )
  530. {
  531. NavConnect *connect = &m_connect[ d ][ it ];
  532. unsigned int id = connect->id;
  533. connect->area = TheNavMesh->GetNavAreaByID( id );
  534. if (id && connect->area == NULL)
  535. {
  536. Msg( "CNavArea::PostLoad: Corrupt navigation data. Cannot connect Navigation Areas.\n" );
  537. error = NAV_CORRUPT_DATA;
  538. }
  539. }
  540. }
  541. // resolve approach area IDs
  542. for( int a=0; a<m_approachCount; ++a )
  543. {
  544. m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
  545. if (m_approach[a].here.id && m_approach[a].here.area == NULL)
  546. {
  547. Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
  548. error = NAV_CORRUPT_DATA;
  549. }
  550. m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
  551. if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
  552. {
  553. Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
  554. error = NAV_CORRUPT_DATA;
  555. }
  556. m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
  557. if (m_approach[a].next.id && m_approach[a].next.area == NULL)
  558. {
  559. Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
  560. error = NAV_CORRUPT_DATA;
  561. }
  562. }
  563. // resolve spot encounter IDs
  564. SpotEncounter *e;
  565. FOR_EACH_VEC( m_spotEncounters, it )
  566. {
  567. e = m_spotEncounters[ it ];
  568. e->from.area = TheNavMesh->GetNavAreaByID( e->from.id );
  569. if (e->from.area == NULL)
  570. {
  571. Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing \"from\" Navigation Area for Encounter Spot.\n" );
  572. error = NAV_CORRUPT_DATA;
  573. }
  574. e->to.area = TheNavMesh->GetNavAreaByID( e->to.id );
  575. if (e->to.area == NULL)
  576. {
  577. Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing \"to\" Navigation Area for Encounter Spot.\n" );
  578. error = NAV_CORRUPT_DATA;
  579. }
  580. if (e->from.area && e->to.area)
  581. {
  582. // compute path
  583. float halfWidth;
  584. ComputePortal( e->to.area, e->toDir, &e->path.to, &halfWidth );
  585. ComputePortal( e->from.area, e->fromDir, &e->path.from, &halfWidth );
  586. const float eyeHeight = HalfHumanHeight;
  587. e->path.from.z = e->from.area->GetZ( e->path.from ) + eyeHeight;
  588. e->path.to.z = e->to.area->GetZ( e->path.to ) + eyeHeight;
  589. }
  590. // resolve HidingSpot IDs
  591. FOR_EACH_VEC( e->spots, sit )
  592. {
  593. SpotOrder *order = &e->spots[ sit ];
  594. order->spot = GetHidingSpotByID( order->id );
  595. if (order->spot == NULL)
  596. {
  597. Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Hiding Spot\n" );
  598. error = NAV_CORRUPT_DATA;
  599. }
  600. }
  601. }
  602. // build overlap list
  603. /// @todo Optimize this
  604. FOR_EACH_VEC( TheNavAreas, nit )
  605. {
  606. CNavArea *area = TheNavAreas[ nit ];
  607. if (area == this)
  608. continue;
  609. if (IsOverlapping( area ))
  610. m_overlappingAreas.AddToTail( area );
  611. }
  612. return error;
  613. }
  614. //--------------------------------------------------------------------------------------------------------------
  615. /**
  616. * Determine the earliest time this hiding spot can be reached by either team
  617. */
  618. void CNavArea::ComputeEarliestOccupyTimes( void )
  619. {
  620. #ifdef CSTRIKE_DLL
  621. /// @todo Derive cstrike-specific navigation classes
  622. for( int i=0; i<MAX_NAV_TEAMS; ++i )
  623. {
  624. // no spot in the map should take longer than this to reach
  625. m_earliestOccupyTime[i] = 120.0f;
  626. }
  627. if (nav_quicksave.GetBool())
  628. return;
  629. // maximum player speed in units/second
  630. const float playerSpeed = 240.0f;
  631. ShortestPathCost cost;
  632. CBaseEntity *spot;
  633. // determine the shortest time it will take a Terrorist to reach this area
  634. int team = TEAM_TERRORIST % MAX_NAV_TEAMS;
  635. for( spot = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
  636. spot;
  637. spot = gEntList.FindEntityByClassname( spot, "info_player_terrorist" ) )
  638. {
  639. float travelDistance = NavAreaTravelDistance( spot->GetAbsOrigin(), m_center, cost );
  640. if (travelDistance < 0.0f)
  641. continue;
  642. float travelTime = travelDistance / playerSpeed;
  643. if (travelTime < m_earliestOccupyTime[ team ])
  644. {
  645. m_earliestOccupyTime[ team ] = travelTime;
  646. }
  647. }
  648. // determine the shortest time it will take a CT to reach this area
  649. team = TEAM_CT % MAX_NAV_TEAMS;
  650. for( spot = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
  651. spot;
  652. spot = gEntList.FindEntityByClassname( spot, "info_player_counterterrorist" ) )
  653. {
  654. float travelDistance = NavAreaTravelDistance( spot->GetAbsOrigin(), m_center, cost );
  655. if (travelDistance < 0.0f)
  656. continue;
  657. float travelTime = travelDistance / playerSpeed;
  658. if (travelTime < m_earliestOccupyTime[ team ])
  659. {
  660. m_earliestOccupyTime[ team ] = travelTime;
  661. }
  662. }
  663. #else
  664. for( int i=0; i<MAX_NAV_TEAMS; ++i )
  665. {
  666. m_earliestOccupyTime[i] = 0.0f;
  667. }
  668. #endif
  669. }
  670. //--------------------------------------------------------------------------------------------------------------
  671. /**
  672. * Determine if this area is a "battlefront" area - where two rushing teams first meet.
  673. */
  674. void CNavMesh::ComputeBattlefrontAreas( void )
  675. {
  676. #if 0
  677. #ifdef CSTRIKE_DLL
  678. ShortestPathCost cost;
  679. CBaseEntity *tSpawn, *ctSpawn;
  680. for( tSpawn = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
  681. tSpawn;
  682. tSpawn = gEntList.FindEntityByClassname( tSpawn, "info_player_terrorist" ) )
  683. {
  684. CNavArea *tArea = TheNavMesh->GetNavArea( tSpawn->GetAbsOrigin() );
  685. if (tArea == NULL)
  686. continue;
  687. for( ctSpawn = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
  688. ctSpawn;
  689. ctSpawn = gEntList.FindEntityByClassname( ctSpawn, "info_player_counterterrorist" ) )
  690. {
  691. CNavArea *ctArea = TheNavMesh->GetNavArea( ctSpawn->GetAbsOrigin() );
  692. if (ctArea == NULL)
  693. continue;
  694. if (tArea == ctArea)
  695. {
  696. m_isBattlefront = true;
  697. return;
  698. }
  699. // build path between these two spawn points - assume if path fails, it at least got close
  700. // (ie: imagine spawn points that you jump down from - can't path to)
  701. CNavArea *goalArea = NULL;
  702. NavAreaBuildPath( tArea, ctArea, NULL, cost, &goalArea );
  703. if (goalArea == NULL)
  704. continue;
  705. /**
  706. * @todo Need to enumerate ALL paths between all pairs of spawn points to find all battlefront areas
  707. */
  708. // find the area with the earliest overlapping occupy times
  709. CNavArea *battlefront = NULL;
  710. float earliestTime = 999999.9f;
  711. const float epsilon = 1.0f;
  712. CNavArea *area;
  713. for( area = goalArea; area; area = area->GetParent() )
  714. {
  715. if (fabs(area->GetEarliestOccupyTime( TEAM_TERRORIST ) - area->GetEarliestOccupyTime( TEAM_CT )) < epsilon)
  716. {
  717. }
  718. }
  719. }
  720. }
  721. #endif
  722. #endif
  723. }
  724. //--------------------------------------------------------------------------------------------------------------
  725. /**
  726. * Return the filename for this map's "nav map" file
  727. */
  728. const char *CNavMesh::GetFilename( void ) const
  729. {
  730. // filename is local to game dir for Steam, so we need to prepend game dir for regular file save
  731. char gamePath[256];
  732. engine->GetGameDir( gamePath, 256 );
  733. static char filename[256];
  734. Q_snprintf( filename, sizeof( filename ), "%s\\maps\\%s.nav", gamePath, STRING( gpGlobals->mapname ) );
  735. return filename;
  736. }
  737. //--------------------------------------------------------------------------------------------------------------
  738. /*
  739. ============
  740. COM_FixSlashes
  741. Changes all '/' characters into '\' characters, in place.
  742. ============
  743. */
  744. inline void COM_FixSlashes( char *pname )
  745. {
  746. #ifdef _WIN32
  747. while ( *pname )
  748. {
  749. if ( *pname == '/' )
  750. *pname = '\\';
  751. pname++;
  752. }
  753. #else
  754. while ( *pname )
  755. {
  756. if ( *pname == '\\' )
  757. *pname = '/';
  758. pname++;
  759. }
  760. #endif
  761. }
  762. static void WarnIfMeshNeedsAnalysis( void )
  763. {
  764. // Quick check to warn about needing to analyze: nav_strip, nav_delete, etc set
  765. // every CNavArea's m_approachCount to 0, and delete their m_spotEncounterList.
  766. // So, if no area has either, odds are good we need an analyze.
  767. {
  768. bool hasApproachAreas = false;
  769. bool hasSpotEncounters = false;
  770. FOR_EACH_VEC( TheNavAreas, it )
  771. {
  772. CNavArea *area = TheNavAreas[ it ];
  773. if ( area->GetApproachInfoCount() )
  774. {
  775. hasApproachAreas = true;
  776. }
  777. if ( area->GetSpotEncounterCount() )
  778. {
  779. hasSpotEncounters = true;
  780. }
  781. }
  782. if ( !hasApproachAreas || !hasSpotEncounters )
  783. {
  784. Warning( "The nav mesh needs a full nav_analyze\n" );
  785. }
  786. }
  787. }
  788. /**
  789. * Store Navigation Mesh to a file
  790. */
  791. bool CNavMesh::Save( void ) const
  792. {
  793. WarnIfMeshNeedsAnalysis();
  794. const char *filename = GetFilename();
  795. if (filename == NULL)
  796. return false;
  797. //
  798. // Store the NAV file
  799. //
  800. COM_FixSlashes( const_cast<char *>(filename) );
  801. // get size of source bsp file for later (before we open the nav file for writing, in
  802. // case of failure)
  803. char *bspFilename = GetBspFilename( filename );
  804. if (bspFilename == NULL)
  805. {
  806. return false;
  807. }
  808. FileHandle_t file = filesystem->Open( filename, "wb" );
  809. if (!file)
  810. {
  811. return false;
  812. }
  813. // store "magic number" to help identify this kind of file
  814. unsigned int magic = NAV_MAGIC_NUMBER;
  815. filesystem->Write( &magic, sizeof(unsigned int), file );
  816. // store version number of file
  817. // 1 = hiding spots as plain vector array
  818. // 2 = hiding spots as HidingSpot objects
  819. // 3 = Encounter spots use HidingSpot ID's instead of storing vector again
  820. // 4 = Includes size of source bsp file to verify nav data correlation
  821. // ---- Beta Release at V4 -----
  822. // 5 = Added Place info
  823. // ---- Conversion to Src ------
  824. // 6 = Added Ladder info
  825. // 7 = Areas store ladder ID's so ladders can have one-way connections
  826. // 8 = Added earliest occupy times (2 floats) to each area
  827. // 9 = Promoted CNavArea's attribute flags to a short
  828. unsigned int version = NavCurrentVersion;
  829. filesystem->Write( &version, sizeof(unsigned int), file );
  830. // store the size of source bsp file in the nav file
  831. // so we can test if the bsp changed since the nav file was made
  832. unsigned int bspSize = filesystem->Size( bspFilename );
  833. DevMsg( "Size of bsp file '%s' is %u bytes.\n", bspFilename, bspSize );
  834. filesystem->Write( &bspSize, sizeof(unsigned int), file );
  835. //
  836. // Build a directory of the Places in this map
  837. //
  838. placeDirectory.Reset();
  839. FOR_EACH_VEC( TheNavAreas, nit )
  840. {
  841. CNavArea *area = TheNavAreas[ nit ];
  842. Place place = area->GetPlace();
  843. if (place)
  844. {
  845. placeDirectory.AddPlace( place );
  846. }
  847. }
  848. placeDirectory.Save( file );
  849. //
  850. // Store navigation areas
  851. //
  852. {
  853. // store number of areas
  854. unsigned int count = TheNavAreas.Count();
  855. filesystem->Write( &count, sizeof(unsigned int), file );
  856. // store each area
  857. FOR_EACH_VEC( TheNavAreas, it )
  858. {
  859. CNavArea *area = TheNavAreas[ it ];
  860. area->Save( file, version );
  861. }
  862. }
  863. //
  864. // Store ladders
  865. //
  866. {
  867. // store number of ladders
  868. unsigned int count = m_ladders.Count();
  869. filesystem->Write( &count, sizeof(unsigned int), file );
  870. // store each ladder
  871. FOR_EACH_VEC( m_ladders, it )
  872. {
  873. CNavLadder *ladder = m_ladders[ it ];
  874. ladder->Save( file, version );
  875. }
  876. }
  877. filesystem->Flush( file );
  878. filesystem->Close( file );
  879. unsigned int navSize = filesystem->Size( filename );
  880. DevMsg( "Size of nav file '%s' is %u bytes.\n", filename, navSize );
  881. return true;
  882. }
  883. //--------------------------------------------------------------------------------------------------------------
  884. static NavErrorType CheckNavFile( const char *bspFilename )
  885. {
  886. if ( !bspFilename )
  887. return NAV_CANT_ACCESS_FILE;
  888. char bspPathname[256];
  889. char filename[256];
  890. Q_strncpy( bspPathname, "maps/", sizeof( bspPathname ) );
  891. Q_strncat( bspPathname, bspFilename, sizeof( bspPathname ), COPY_ALL_CHARACTERS );
  892. Q_strncpy( filename, bspPathname, sizeof( filename ) );
  893. Q_SetExtension( filename, ".nav", sizeof( filename ) );
  894. bool navIsInBsp = false;
  895. FileHandle_t file = filesystem->Open( filename, "rb", "MOD" ); // this ignores .nav files embedded in the .bsp ...
  896. if ( !file )
  897. {
  898. navIsInBsp = true;
  899. file = filesystem->Open( filename, "rb", "GAME" ); // ... and this looks for one if it's the only one around.
  900. }
  901. if (!file)
  902. {
  903. return NAV_CANT_ACCESS_FILE;
  904. }
  905. // check magic number
  906. int result;
  907. unsigned int magic;
  908. result = filesystem->Read( &magic, sizeof(unsigned int), file );
  909. if (!result || magic != NAV_MAGIC_NUMBER)
  910. {
  911. filesystem->Close( file );
  912. return NAV_INVALID_FILE;
  913. }
  914. // read file version number
  915. unsigned int version;
  916. result = filesystem->Read( &version, sizeof(unsigned int), file );
  917. if (!result || version > NavCurrentVersion || version < 4)
  918. {
  919. filesystem->Close( file );
  920. return NAV_BAD_FILE_VERSION;
  921. }
  922. // get size of source bsp file and verify that the bsp hasn't changed
  923. unsigned int saveBspSize;
  924. filesystem->Read( &saveBspSize, sizeof(unsigned int), file );
  925. // verify size
  926. unsigned int bspSize = filesystem->Size( bspPathname );
  927. if (bspSize != saveBspSize && !navIsInBsp)
  928. {
  929. return NAV_FILE_OUT_OF_DATE;
  930. }
  931. return NAV_OK;
  932. }
  933. //--------------------------------------------------------------------------------------------------------------
  934. void CommandNavCheckFileConsistency( void )
  935. {
  936. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  937. return;
  938. FileFindHandle_t findHandle;
  939. const char *bspFilename = filesystem->FindFirstEx( "maps/*.bsp", "MOD", &findHandle );
  940. while ( bspFilename )
  941. {
  942. switch ( CheckNavFile( bspFilename ) )
  943. {
  944. case NAV_CANT_ACCESS_FILE:
  945. Warning( "Missing nav file for %s\n", bspFilename );
  946. break;
  947. case NAV_INVALID_FILE:
  948. Warning( "Invalid nav file for %s\n", bspFilename );
  949. break;
  950. case NAV_BAD_FILE_VERSION:
  951. Warning( "Old nav file for %s\n", bspFilename );
  952. break;
  953. case NAV_FILE_OUT_OF_DATE:
  954. Warning( "The nav file for %s is built from an old version of the map\n", bspFilename );
  955. break;
  956. case NAV_OK:
  957. Msg( "The nav file for %s is up-to-date\n", bspFilename );
  958. break;
  959. }
  960. bspFilename = filesystem->FindNext( findHandle );
  961. }
  962. filesystem->FindClose( findHandle );
  963. }
  964. static ConCommand nav_check_file_consistency( "nav_check_file_consistency", CommandNavCheckFileConsistency, "Scans the maps directory and reports any missing/out-of-date navigation files.", FCVAR_GAMEDLL | FCVAR_CHEAT );
  965. //--------------------------------------------------------------------------------------------------------------
  966. /**
  967. * Load AI navigation data from a file
  968. */
  969. NavErrorType CNavMesh::Load( void )
  970. {
  971. // free previous navigation mesh data
  972. Reset();
  973. placeDirectory.Reset();
  974. CNavArea::m_nextID = 1;
  975. // nav filename is derived from map filename
  976. char filename[256];
  977. Q_snprintf( filename, sizeof( filename ), "maps\\%s.nav", STRING( gpGlobals->mapname ) );
  978. bool navIsInBsp = false;
  979. FileHandle_t file = filesystem->Open( filename, "rb", "MOD" ); // this ignores .nav files embedded in the .bsp ...
  980. if ( !file )
  981. {
  982. navIsInBsp = true;
  983. file = filesystem->Open( filename, "rb", "GAME" ); // ... and this looks for one if it's the only one around.
  984. }
  985. if (!file)
  986. {
  987. return NAV_CANT_ACCESS_FILE;
  988. }
  989. // check magic number
  990. int result;
  991. unsigned int magic;
  992. result = filesystem->Read( &magic, sizeof(unsigned int), file );
  993. if (!result || magic != NAV_MAGIC_NUMBER)
  994. {
  995. Msg( "Invalid navigation file '%s'.\n", filename );
  996. filesystem->Close( file );
  997. return NAV_INVALID_FILE;
  998. }
  999. // read file version number
  1000. unsigned int version;
  1001. result = filesystem->Read( &version, sizeof(unsigned int), file );
  1002. if (!result || version > NavCurrentVersion)
  1003. {
  1004. Msg( "Unknown navigation file version.\n" );
  1005. filesystem->Close( file );
  1006. return NAV_BAD_FILE_VERSION;
  1007. }
  1008. if (version >= 4)
  1009. {
  1010. // get size of source bsp file and verify that the bsp hasn't changed
  1011. unsigned int saveBspSize;
  1012. filesystem->Read( &saveBspSize, sizeof(unsigned int), file );
  1013. // verify size
  1014. char *bspFilename = GetBspFilename( filename );
  1015. if ( bspFilename == NULL )
  1016. {
  1017. filesystem->Close( file );
  1018. return NAV_INVALID_FILE;
  1019. }
  1020. unsigned int bspSize = filesystem->Size( bspFilename );
  1021. if (bspSize != saveBspSize && !navIsInBsp)
  1022. {
  1023. if ( engine->IsDedicatedServer() )
  1024. {
  1025. // Warning doesn't print to the dedicated server console, so we'll use Msg instead
  1026. Msg( "The Navigation Mesh was built using a different version of this map.\n" );
  1027. }
  1028. else
  1029. {
  1030. Warning( "The Navigation Mesh was built using a different version of this map.\n" );
  1031. }
  1032. m_isFromCurrentMap = false;
  1033. }
  1034. }
  1035. // load Place directory
  1036. if (version >= 5)
  1037. {
  1038. placeDirectory.Load( file );
  1039. }
  1040. // get number of areas
  1041. unsigned int count;
  1042. unsigned int i;
  1043. result = filesystem->Read( &count, sizeof(unsigned int), file );
  1044. Extent extent;
  1045. extent.lo.x = 9999999999.9f;
  1046. extent.lo.y = 9999999999.9f;
  1047. extent.hi.x = -9999999999.9f;
  1048. extent.hi.y = -9999999999.9f;
  1049. // load the areas and compute total extent
  1050. for( i=0; i<count; ++i )
  1051. {
  1052. CNavArea *area = new CNavArea;
  1053. area->Load( file, version );
  1054. TheNavAreas.AddToTail( area );
  1055. Extent areaExtent;
  1056. area->GetExtent(&areaExtent);
  1057. // check validity of nav area
  1058. if (areaExtent.lo.x >= areaExtent.hi.x || areaExtent.lo.y >= areaExtent.hi.y)
  1059. Warning( "WARNING: Degenerate Navigation Area #%d at ( %g, %g, %g )\n",
  1060. area->GetID(), area->m_center.x, area->m_center.y, area->m_center.z );
  1061. if (areaExtent.lo.x < extent.lo.x)
  1062. extent.lo.x = areaExtent.lo.x;
  1063. if (areaExtent.lo.y < extent.lo.y)
  1064. extent.lo.y = areaExtent.lo.y;
  1065. if (areaExtent.hi.x > extent.hi.x)
  1066. extent.hi.x = areaExtent.hi.x;
  1067. if (areaExtent.hi.y > extent.hi.y)
  1068. extent.hi.y = areaExtent.hi.y;
  1069. }
  1070. // add the areas to the grid
  1071. AllocateGrid( extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y );
  1072. FOR_EACH_VEC( TheNavAreas, it )
  1073. {
  1074. AddNavArea( TheNavAreas[ it ] );
  1075. }
  1076. //
  1077. // Set up all the ladders
  1078. //
  1079. if (version >= 6)
  1080. {
  1081. result = filesystem->Read( &count, sizeof(unsigned int), file );
  1082. // load the ladders
  1083. for( i=0; i<count; ++i )
  1084. {
  1085. CNavLadder *ladder = new CNavLadder;
  1086. ladder->Load( file, version );
  1087. m_ladders.AddToTail( ladder );
  1088. }
  1089. }
  1090. else
  1091. {
  1092. BuildLadders();
  1093. }
  1094. // allow areas to connect to each other, etc
  1095. FOR_EACH_VEC( TheNavAreas, pit )
  1096. {
  1097. CNavArea *area = TheNavAreas[ pit ];
  1098. area->PostLoad();
  1099. }
  1100. // allow hiding spots to compute information
  1101. FOR_EACH_VEC( TheHidingSpots, hit )
  1102. {
  1103. HidingSpot *spot = TheHidingSpots[ hit ];
  1104. spot->PostLoad();
  1105. }
  1106. if ( version < 8 )
  1107. {
  1108. // Old nav meshes need to compute earliest occupy times
  1109. FOR_EACH_VEC( TheNavAreas, nit )
  1110. {
  1111. CNavArea *area = TheNavAreas[ nit ];
  1112. area->ComputeEarliestOccupyTimes();
  1113. }
  1114. }
  1115. ComputeBattlefrontAreas();
  1116. // the Navigation Mesh has been successfully loaded
  1117. m_isLoaded = true;
  1118. filesystem->Close( file );
  1119. WarnIfMeshNeedsAnalysis();
  1120. return NAV_OK;
  1121. }
  1122. #endif