diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/nav_file.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/nav_file.cpp')
| -rw-r--r-- | mp/src/game/server/nav_file.cpp | 1696 |
1 files changed, 1696 insertions, 0 deletions
diff --git a/mp/src/game/server/nav_file.cpp b/mp/src/game/server/nav_file.cpp new file mode 100644 index 00000000..e07e7f15 --- /dev/null +++ b/mp/src/game/server/nav_file.cpp @@ -0,0 +1,1696 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_file.cpp
+// Reading and writing nav files
+// Author: Michael S. Booth ([email protected]), January-September 2003
+
+#include "cbase.h"
+#include "nav_mesh.h"
+#include "gamerules.h"
+#include "datacache/imdlcache.h"
+
+#ifdef TERROR
+#include "func_elevator.h"
+#endif
+
+#include "tier1/lzmaDecoder.h"
+
+#ifdef CSTRIKE_DLL
+#include "cs_shareddefs.h"
+#include "nav_pathfind.h"
+#include "cs_nav_area.h"
+#endif
+
+// NOTE: This has to be the last file included!
+#include "tier0/memdbgon.h"
+
+
+//--------------------------------------------------------------------------------------------------------------
+/// The current version of the nav file format
+
+/// IMPORTANT: If this version changes, the swap function in makegamedata
+/// must be updated to match. If not, this will break the Xbox 360.
+// TODO: Was changed from 15, update when latest 360 code is integrated (MSB 5/5/09)
+const int NavCurrentVersion = 16;
+
+//--------------------------------------------------------------------------------------------------------------
+//
+// The 'place directory' is used to save and load places from
+// nav files in a size-efficient manner that also allows for the
+// order of the place ID's to change without invalidating the
+// nav files.
+//
+// The place directory is stored in the nav file as a list of
+// place name strings. Each nav area then contains an index
+// into that directory, or zero if no place has been assigned to
+// that area.
+//
+PlaceDirectory::PlaceDirectory( void )
+{
+ Reset();
+}
+
+void PlaceDirectory::Reset( void )
+{
+ m_directory.RemoveAll();
+ m_hasUnnamedAreas = false;
+}
+
+/// return true if this place is already in the directory
+bool PlaceDirectory::IsKnown( Place place ) const
+{
+ return m_directory.HasElement( place );
+}
+
+/// return the directory index corresponding to this Place (0 = no entry)
+PlaceDirectory::IndexType PlaceDirectory::GetIndex( Place place ) const
+{
+ if (place == UNDEFINED_PLACE)
+ return 0;
+
+ int i = m_directory.Find( place );
+
+ if (i < 0)
+ {
+ AssertMsg( false, "PlaceDirectory::GetIndex failure" );
+ return 0;
+ }
+
+ return (IndexType)(i+1);
+}
+
+/// add the place to the directory if not already known
+void PlaceDirectory::AddPlace( Place place )
+{
+ if (place == UNDEFINED_PLACE)
+ {
+ m_hasUnnamedAreas = true;
+ return;
+ }
+
+ Assert( place < 1000 );
+
+ if (IsKnown( place ))
+ return;
+
+ m_directory.AddToTail( place );
+}
+
+/// given an index, return the Place
+Place PlaceDirectory::IndexToPlace( IndexType entry ) const
+{
+ if (entry == 0)
+ return UNDEFINED_PLACE;
+
+ int i = entry-1;
+
+ if (i >= m_directory.Count())
+ {
+ AssertMsg( false, "PlaceDirectory::IndexToPlace: Invalid entry" );
+ return UNDEFINED_PLACE;
+ }
+
+ return m_directory[ i ];
+}
+
+/// store the directory
+void PlaceDirectory::Save( CUtlBuffer &fileBuffer )
+{
+ // store number of entries in directory
+ IndexType count = (IndexType)m_directory.Count();
+ fileBuffer.PutUnsignedShort( count );
+
+ // store entries
+ for( int i=0; i<m_directory.Count(); ++i )
+ {
+ const char *placeName = TheNavMesh->PlaceToName( m_directory[i] );
+
+ // store string length followed by string itself
+ unsigned short len = (unsigned short)(strlen( placeName ) + 1);
+ fileBuffer.PutUnsignedShort( len );
+ fileBuffer.Put( placeName, len );
+ }
+
+ fileBuffer.PutUnsignedChar( m_hasUnnamedAreas );
+}
+
+/// load the directory
+void PlaceDirectory::Load( CUtlBuffer &fileBuffer, int version )
+{
+ // read number of entries
+ IndexType count = fileBuffer.GetUnsignedShort();
+
+ m_directory.RemoveAll();
+
+ // read each entry
+ char placeName[256];
+ unsigned short len;
+ for( int i=0; i<count; ++i )
+ {
+ len = fileBuffer.GetUnsignedShort();
+ fileBuffer.Get( placeName, MIN( sizeof( placeName ), len ) );
+
+ Place place = TheNavMesh->NameToPlace( placeName );
+ if (place == UNDEFINED_PLACE)
+ {
+ Warning( "Warning: NavMesh place %s is undefined?\n", placeName );
+ }
+ AddPlace( place );
+ }
+
+ if ( version > 11 )
+ {
+ m_hasUnnamedAreas = fileBuffer.GetUnsignedChar() != 0;
+ }
+}
+
+
+
+PlaceDirectory placeDirectory;
+
+#if defined( _X360 )
+ #define FORMAT_BSPFILE "maps\\%s.360.bsp"
+ #define FORMAT_NAVFILE "maps\\%s.360.nav"
+#else
+ #define FORMAT_BSPFILE "maps\\%s.bsp"
+ #define FORMAT_NAVFILE "maps\\%s.nav"
+#endif
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Replace extension with "bsp"
+ */
+char *GetBspFilename( const char *navFilename )
+{
+ static char bspFilename[256];
+
+ Q_snprintf( bspFilename, sizeof( bspFilename ), FORMAT_BSPFILE, STRING( gpGlobals->mapname ) );
+
+ int len = strlen( bspFilename );
+ if (len < 3)
+ return NULL;
+
+ bspFilename[ len-3 ] = 'b';
+ bspFilename[ len-2 ] = 's';
+ bspFilename[ len-1 ] = 'p';
+
+ return bspFilename;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Save a navigation area to the opened binary stream
+ */
+void CNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
+{
+ // save ID
+ fileBuffer.PutUnsignedInt( m_id );
+
+ // save attribute flags
+ fileBuffer.PutInt( m_attributeFlags );
+
+ // save extent of area
+ fileBuffer.Put( &m_nwCorner, 3*sizeof(float) );
+ fileBuffer.Put( &m_seCorner, 3*sizeof(float) );
+
+ // save heights of implicit corners
+ fileBuffer.PutFloat( m_neZ );
+ fileBuffer.PutFloat( m_swZ );
+
+ // save connections to adjacent areas
+ // in the enum order NORTH, EAST, SOUTH, WEST
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ // save number of connections for this direction
+ unsigned int count = m_connect[d].Count();
+ fileBuffer.PutUnsignedInt( count );
+
+ FOR_EACH_VEC( m_connect[d], it )
+ {
+ NavConnect connect = m_connect[d][ it ];
+ fileBuffer.PutUnsignedInt( connect.area->m_id );
+ }
+ }
+
+ //
+ // Store hiding spots for this area
+ //
+ unsigned char count;
+ if (m_hidingSpots.Count() > 255)
+ {
+ count = 255;
+ Warning( "Warning: NavArea #%d: Truncated hiding spot list to 255\n", m_id );
+ }
+ else
+ {
+ count = (unsigned char)m_hidingSpots.Count();
+ }
+ fileBuffer.PutUnsignedChar( count );
+
+ // store HidingSpot objects
+ unsigned int saveCount = 0;
+ FOR_EACH_VEC( m_hidingSpots, hit )
+ {
+ HidingSpot *spot = m_hidingSpots[ hit ];
+
+ spot->Save( fileBuffer, version );
+
+ // overflow check
+ if (++saveCount == count)
+ break;
+ }
+
+ //
+ // Save encounter spots for this area
+ //
+ {
+ // save number of encounter paths for this area
+ unsigned int count = m_spotEncounters.Count();
+ fileBuffer.PutUnsignedInt( count );
+
+ SpotEncounter *e;
+ FOR_EACH_VEC( m_spotEncounters, it )
+ {
+ e = m_spotEncounters[ it ];
+
+ if (e->from.area)
+ fileBuffer.PutUnsignedInt( e->from.area->m_id );
+ else
+ fileBuffer.PutUnsignedInt( 0 );
+
+ unsigned char dir = (unsigned char)e->fromDir;
+ fileBuffer.PutUnsignedChar( dir );
+
+ if (e->to.area)
+ fileBuffer.PutUnsignedInt( e->to.area->m_id );
+ else
+ fileBuffer.PutUnsignedInt( 0 );
+
+ dir = (unsigned char)e->toDir;
+ fileBuffer.PutUnsignedChar( dir );
+
+ // write list of spots along this path
+ unsigned char spotCount;
+ if (e->spots.Count() > 255)
+ {
+ spotCount = 255;
+ Warning( "Warning: NavArea #%d: Truncated encounter spot list to 255\n", m_id );
+ }
+ else
+ {
+ spotCount = (unsigned char)e->spots.Count();
+ }
+ fileBuffer.PutUnsignedChar( spotCount );
+
+ saveCount = 0;
+ FOR_EACH_VEC( e->spots, sit )
+ {
+ SpotOrder *order = &e->spots[ sit ];
+
+ // order->spot may be NULL if we've loaded a nav mesh that has been edited but not re-analyzed
+ unsigned int id = (order->spot) ? order->spot->GetID() : 0;
+ fileBuffer.PutUnsignedInt( id );
+
+ unsigned char t = (unsigned char)(255 * order->t);
+ fileBuffer.PutUnsignedChar( t );
+
+ // overflow check
+ if (++saveCount == spotCount)
+ break;
+ }
+ }
+ }
+
+ // store place dictionary entry
+ PlaceDirectory::IndexType entry = placeDirectory.GetIndex( GetPlace() );
+ fileBuffer.Put( &entry, sizeof(entry) );
+
+ // write out ladder info
+ int i;
+ for ( i=0; i<CNavLadder::NUM_LADDER_DIRECTIONS; ++i )
+ {
+ // save number of encounter paths for this area
+ unsigned int count = m_ladder[i].Count();
+ fileBuffer.PutUnsignedInt( count );
+
+ NavLadderConnect ladder;
+ FOR_EACH_VEC( m_ladder[i], it )
+ {
+ ladder = m_ladder[i][it];
+
+ unsigned int id = ladder.ladder->GetID();
+ fileBuffer.PutUnsignedInt( id );
+ }
+ }
+
+ // save earliest occupy times
+ for( i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ // no spot in the map should take longer than this to reach
+ fileBuffer.Put( &m_earliestOccupyTime[i], sizeof(m_earliestOccupyTime[i]) );
+ }
+
+ // save light intensity
+ for ( i=0; i<NUM_CORNERS; ++i )
+ {
+ fileBuffer.PutFloat( m_lightIntensity[i] );
+ }
+
+ // save visible area set
+ unsigned int visibleAreaCount = m_potentiallyVisibleAreas.Count();
+ fileBuffer.PutUnsignedInt( visibleAreaCount );
+
+ for ( int vit=0; vit<m_potentiallyVisibleAreas.Count(); ++vit )
+ {
+ CNavArea *area = m_potentiallyVisibleAreas[ vit ].area;
+
+ unsigned int id = area ? area->GetID() : 0;
+
+ fileBuffer.PutUnsignedInt( id );
+ fileBuffer.PutUnsignedChar( m_potentiallyVisibleAreas[ vit ].attributes );
+ }
+
+ // store area we inherit visibility from
+ unsigned int id = ( m_inheritVisibilityFrom.area ) ? m_inheritVisibilityFrom.area->GetID() : 0;
+ fileBuffer.PutUnsignedInt( id );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Load a navigation area from the file
+ */
+NavErrorType CNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
+{
+ // load ID
+ m_id = fileBuffer.GetUnsignedInt();
+
+ // update nextID to avoid collisions
+ if (m_id >= m_nextID)
+ m_nextID = m_id+1;
+
+ // load attribute flags
+ if ( version <= 8 )
+ {
+ m_attributeFlags = fileBuffer.GetUnsignedChar();
+ }
+ else if ( version < 13 )
+ {
+ m_attributeFlags = fileBuffer.GetUnsignedShort();
+ }
+ else
+ {
+ m_attributeFlags = fileBuffer.GetInt();
+ }
+
+ // load extent of area
+ fileBuffer.Get( &m_nwCorner, 3*sizeof(float) );
+ fileBuffer.Get( &m_seCorner, 3*sizeof(float) );
+
+ m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
+ m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
+ m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
+
+ if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
+ {
+ m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
+ m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
+ }
+ else
+ {
+ m_invDxCorners = m_invDyCorners = 0;
+
+ DevWarning( "Degenerate Navigation Area #%d at setpos %g %g %g\n",
+ m_id, m_center.x, m_center.y, m_center.z );
+ }
+
+ // load heights of implicit corners
+ m_neZ = fileBuffer.GetFloat();
+ m_swZ = fileBuffer.GetFloat();
+
+ CheckWaterLevel();
+
+ // load connections (IDs) to adjacent areas
+ // in the enum order NORTH, EAST, SOUTH, WEST
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ // load number of connections for this direction
+ unsigned int count = fileBuffer.GetUnsignedInt();
+ Assert( fileBuffer.IsValid() );
+
+ m_connect[d].EnsureCapacity( count );
+ for( unsigned int i=0; i<count; ++i )
+ {
+ NavConnect connect;
+ connect.id = fileBuffer.GetUnsignedInt();
+ Assert( fileBuffer.IsValid() );
+
+ // don't allow self-referential connections
+ if ( connect.id != m_id )
+ {
+ m_connect[d].AddToTail( connect );
+ }
+ }
+ }
+
+ //
+ // Load hiding spots
+ //
+
+ // load number of hiding spots
+ unsigned char hidingSpotCount = fileBuffer.GetUnsignedChar();
+
+ if (version == 1)
+ {
+ // load simple vector array
+ Vector pos;
+ for( int h=0; h<hidingSpotCount; ++h )
+ {
+ fileBuffer.Get( &pos, 3 * sizeof(float) );
+
+ // create new hiding spot and put on master list
+ HidingSpot *spot = TheNavMesh->CreateHidingSpot();
+ spot->SetPosition( pos );
+ spot->SetFlags( HidingSpot::IN_COVER );
+ m_hidingSpots.AddToTail( spot );
+ }
+ }
+ else
+ {
+ // load HidingSpot objects for this area
+ for( int h=0; h<hidingSpotCount; ++h )
+ {
+ // create new hiding spot and put on master list
+ HidingSpot *spot = TheNavMesh->CreateHidingSpot();
+
+ spot->Load( fileBuffer, version );
+
+ m_hidingSpots.AddToTail( spot );
+ }
+ }
+
+ if ( version < 15 )
+ {
+ //
+ // Eat the approach areas
+ //
+ int nToEat = fileBuffer.GetUnsignedChar();
+
+ // load approach area info (IDs)
+ for( int a=0; a<nToEat; ++a )
+ {
+ fileBuffer.GetUnsignedInt();
+ fileBuffer.GetUnsignedInt();
+ fileBuffer.GetUnsignedChar();
+ fileBuffer.GetUnsignedInt();
+ fileBuffer.GetUnsignedChar();
+ }
+ }
+
+
+ //
+ // Load encounter paths for this area
+ //
+ unsigned int count = fileBuffer.GetUnsignedInt();
+
+ if (version < 3)
+ {
+ // old data, read and discard
+ for( unsigned int e=0; e<count; ++e )
+ {
+ SpotEncounter encounter;
+
+ encounter.from.id = fileBuffer.GetUnsignedInt();
+ encounter.to.id = fileBuffer.GetUnsignedInt();
+
+ fileBuffer.Get( &encounter.path.from.x, 3 * sizeof(float) );
+ fileBuffer.Get( &encounter.path.to.x, 3 * sizeof(float) );
+
+ // read list of spots along this path
+ unsigned char spotCount = fileBuffer.GetUnsignedChar();
+
+ for( int s=0; s<spotCount; ++s )
+ {
+ fileBuffer.GetFloat();
+ fileBuffer.GetFloat();
+ fileBuffer.GetFloat();
+ fileBuffer.GetFloat();
+ }
+ }
+ return NAV_OK;
+ }
+
+ for( unsigned int e=0; e<count; ++e )
+ {
+ SpotEncounter *encounter = new SpotEncounter;
+
+ encounter->from.id = fileBuffer.GetUnsignedInt();
+
+ unsigned char dir = fileBuffer.GetUnsignedChar();
+ encounter->fromDir = static_cast<NavDirType>( dir );
+
+ encounter->to.id = fileBuffer.GetUnsignedInt();
+
+ dir = fileBuffer.GetUnsignedChar();
+ encounter->toDir = static_cast<NavDirType>( dir );
+
+ // read list of spots along this path
+ unsigned char spotCount = fileBuffer.GetUnsignedChar();
+
+ SpotOrder order;
+ for( int s=0; s<spotCount; ++s )
+ {
+ order.id = fileBuffer.GetUnsignedInt();
+
+ unsigned char t = fileBuffer.GetUnsignedChar();
+
+ order.t = (float)t/255.0f;
+
+ encounter->spots.AddToTail( order );
+ }
+
+ m_spotEncounters.AddToTail( encounter );
+ }
+
+ if (version < 5)
+ return NAV_OK;
+
+ //
+ // Load Place data
+ //
+ PlaceDirectory::IndexType entry = fileBuffer.GetUnsignedShort();
+
+ // convert entry to actual Place
+ SetPlace( placeDirectory.IndexToPlace( entry ) );
+
+ if ( version < 7 )
+ return NAV_OK;
+
+ // load ladder data
+ for ( int dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
+ {
+ count = fileBuffer.GetUnsignedInt();
+ for( unsigned int i=0; i<count; ++i )
+ {
+ NavLadderConnect connect;
+ connect.id = fileBuffer.GetUnsignedInt();
+
+ bool alreadyConnected = false;
+ FOR_EACH_VEC( m_ladder[dir], j )
+ {
+ if ( m_ladder[dir][j].id == connect.id )
+ {
+ alreadyConnected = true;
+ break;
+ }
+ }
+
+ if ( !alreadyConnected )
+ {
+ m_ladder[dir].AddToTail( connect );
+ }
+ }
+ }
+
+ if ( version < 8 )
+ return NAV_OK;
+
+ // load earliest occupy times
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ // no spot in the map should take longer than this to reach
+ m_earliestOccupyTime[i] = fileBuffer.GetFloat();
+ }
+
+ if ( version < 11 )
+ return NAV_OK;
+
+ // load light intensity
+ for ( int i=0; i<NUM_CORNERS; ++i )
+ {
+ m_lightIntensity[i] = fileBuffer.GetFloat();
+ }
+
+ if ( version < 16 )
+ return NAV_OK;
+
+ // load visibility information
+ unsigned int visibleAreaCount = fileBuffer.GetUnsignedInt();
+ if ( !IsX360() )
+ {
+ m_potentiallyVisibleAreas.EnsureCapacity( visibleAreaCount );
+ }
+ else
+ {
+/* TODO: Re-enable when latest 360 code gets integrated (MSB 5/5/09)
+ size_t nBytes = visibleAreaCount * sizeof( AreaBindInfo );
+ m_potentiallyVisibleAreas.~CAreaBindInfoArray();
+ new ( &m_potentiallyVisibleAreas ) CAreaBindInfoArray( (AreaBindInfo *)engine->AllocLevelStaticData( nBytes ), visibleAreaCount );
+*/
+ }
+
+ for( unsigned int j=0; j<visibleAreaCount; ++j )
+ {
+ AreaBindInfo info;
+ info.id = fileBuffer.GetUnsignedInt();
+ info.attributes = fileBuffer.GetUnsignedChar();
+
+ m_potentiallyVisibleAreas.AddToTail( info );
+ }
+
+ // read area from which we inherit visibility
+ m_inheritVisibilityFrom.id = fileBuffer.GetUnsignedInt();
+
+ return NAV_OK;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Convert loaded IDs to pointers
+ * Make sure all IDs are converted, even if corrupt data is encountered.
+ */
+NavErrorType CNavArea::PostLoad( void )
+{
+ NavErrorType error = NAV_OK;
+
+ for ( int dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
+ {
+ FOR_EACH_VEC( m_ladder[dir], it )
+ {
+ NavLadderConnect& connect = m_ladder[dir][it];
+
+ unsigned int id = connect.id;
+
+ if ( TheNavMesh->GetLadders().Find( connect.ladder ) == TheNavMesh->GetLadders().InvalidIndex() )
+ {
+ connect.ladder = TheNavMesh->GetLadderByID( id );
+ }
+
+ if (id && connect.ladder == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation ladder data. Cannot connect Navigation Areas.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ }
+ }
+
+ // connect areas together
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ FOR_EACH_VEC( m_connect[d], it )
+ {
+ NavConnect *connect = &m_connect[ d ][ it ];
+
+ // convert connect ID into an actual area
+ unsigned int id = connect->id;
+ connect->area = TheNavMesh->GetNavAreaByID( id );
+ if (id && connect->area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Cannot connect Navigation Areas.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ connect->length = ( connect->area->GetCenter() - GetCenter() ).Length();
+ }
+ }
+
+ // resolve spot encounter IDs
+ SpotEncounter *e;
+ FOR_EACH_VEC( m_spotEncounters, it )
+ {
+ e = m_spotEncounters[ it ];
+
+ e->from.area = TheNavMesh->GetNavAreaByID( e->from.id );
+ if (e->from.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing \"from\" Navigation Area for Encounter Spot.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ e->to.area = TheNavMesh->GetNavAreaByID( e->to.id );
+ if (e->to.area == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing \"to\" Navigation Area for Encounter Spot.\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+
+ if (e->from.area && e->to.area)
+ {
+ // compute path
+ float halfWidth;
+ ComputePortal( e->to.area, e->toDir, &e->path.to, &halfWidth );
+ ComputePortal( e->from.area, e->fromDir, &e->path.from, &halfWidth );
+
+ const float eyeHeight = HalfHumanHeight;
+ e->path.from.z = e->from.area->GetZ( e->path.from ) + eyeHeight;
+ e->path.to.z = e->to.area->GetZ( e->path.to ) + eyeHeight;
+ }
+
+ // resolve HidingSpot IDs
+ FOR_EACH_VEC( e->spots, sit )
+ {
+ SpotOrder *order = &e->spots[ sit ];
+
+ order->spot = GetHidingSpotByID( order->id );
+ if (order->spot == NULL)
+ {
+ Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Hiding Spot\n" );
+ error = NAV_CORRUPT_DATA;
+ }
+ }
+ }
+
+ // convert visible ID's to pointers to actual areas
+ for ( int it=0; it<m_potentiallyVisibleAreas.Count(); ++it )
+ {
+ AreaBindInfo &info = m_potentiallyVisibleAreas[ it ];
+
+ info.area = TheNavMesh->GetNavAreaByID( info.id );
+ if ( info.area == NULL )
+ {
+ Warning( "Invalid area in visible set for area #%d\n", GetID() );
+ }
+ }
+
+ m_inheritVisibilityFrom.area = TheNavMesh->GetNavAreaByID( m_inheritVisibilityFrom.id );
+ Assert( m_inheritVisibilityFrom.area != this );
+
+ // remove any invalid areas from the list
+ AreaBindInfo bad;
+ bad.area = NULL;
+ while( m_potentiallyVisibleAreas.FindAndRemove( bad ) );
+
+ // func avoid/prefer attributes are controlled by func_nav_cost entities
+ ClearAllNavCostEntities();
+
+ return error;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute travel distance along shortest path from startPos to goalPos.
+ * Return -1 if can't reach endPos from goalPos.
+ */
+template< typename CostFunctor >
+float NavAreaTravelDistance( const Vector &startPos, const Vector &goalPos, CostFunctor &costFunc )
+{
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( startPos );
+ if (startArea == NULL)
+ {
+ return -1.0f;
+ }
+
+ // compute path between areas using given cost heuristic
+ CNavArea *goalArea = NULL;
+ if (NavAreaBuildPath( startArea, NULL, &goalPos, costFunc, &goalArea ) == false)
+ {
+ return -1.0f;
+ }
+
+ // compute distance along path
+ if (goalArea->GetParent() == NULL)
+ {
+ // both points are in the same area - return euclidean distance
+ return (goalPos - startPos).Length();
+ }
+ else
+ {
+ CNavArea *area;
+ float distance;
+
+ // goalPos is assumed to be inside goalArea (or very close to it) - skip to next area
+ area = goalArea->GetParent();
+ distance = (goalPos - area->GetCenter()).Length();
+
+ for( ; area->GetParent(); area = area->GetParent() )
+ {
+ distance += (area->GetCenter() - area->GetParent()->GetCenter()).Length();
+ }
+
+ // add in distance to startPos
+ distance += (startPos - area->GetCenter()).Length();
+
+ return distance;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine the earliest time this hiding spot can be reached by either team
+ */
+void CNavArea::ComputeEarliestOccupyTimes( void )
+{
+#ifdef CSTRIKE_DLL
+ /// @todo Derive cstrike-specific navigation classes
+
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ // no spot in the map should take longer than this to reach
+ m_earliestOccupyTime[i] = 120.0f;
+ }
+
+ if (nav_quicksave.GetBool())
+ return;
+
+ // maximum player speed in units/second
+ const float playerSpeed = 240.0f;
+
+ ShortestPathCost cost;
+ CBaseEntity *spot;
+
+ // determine the shortest time it will take a Terrorist to reach this area
+ int team = TEAM_TERRORIST % MAX_NAV_TEAMS;
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_terrorist" ) )
+ {
+ float travelDistance = NavAreaTravelDistance( spot->GetAbsOrigin(), m_center, cost );
+ if (travelDistance < 0.0f)
+ continue;
+
+ float travelTime = travelDistance / playerSpeed;
+ if (travelTime < m_earliestOccupyTime[ team ])
+ {
+ m_earliestOccupyTime[ team ] = travelTime;
+ }
+ }
+
+
+ // determine the shortest time it will take a CT to reach this area
+ team = TEAM_CT % MAX_NAV_TEAMS;
+ for( spot = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
+ spot;
+ spot = gEntList.FindEntityByClassname( spot, "info_player_counterterrorist" ) )
+ {
+ float travelDistance = NavAreaTravelDistance( spot->GetAbsOrigin(), m_center, cost );
+ if (travelDistance < 0.0f)
+ continue;
+
+ float travelTime = travelDistance / playerSpeed;
+ if (travelTime < m_earliestOccupyTime[ team ])
+ {
+ m_earliestOccupyTime[ team ] = travelTime;
+ }
+ }
+
+#else
+ for( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ m_earliestOccupyTime[i] = 0.0f;
+ }
+#endif
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine if this area is a "battlefront" area - where two rushing teams first meet.
+ */
+void CNavMesh::ComputeBattlefrontAreas( void )
+{
+#if 0
+#ifdef CSTRIKE_DLL
+ ShortestPathCost cost;
+ CBaseEntity *tSpawn, *ctSpawn;
+
+ for( tSpawn = gEntList.FindEntityByClassname( NULL, "info_player_terrorist" );
+ tSpawn;
+ tSpawn = gEntList.FindEntityByClassname( tSpawn, "info_player_terrorist" ) )
+ {
+ CNavArea *tArea = TheNavMesh->GetNavArea( tSpawn->GetAbsOrigin() );
+ if (tArea == NULL)
+ continue;
+
+ for( ctSpawn = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" );
+ ctSpawn;
+ ctSpawn = gEntList.FindEntityByClassname( ctSpawn, "info_player_counterterrorist" ) )
+ {
+ CNavArea *ctArea = TheNavMesh->GetNavArea( ctSpawn->GetAbsOrigin() );
+
+ if (ctArea == NULL)
+ continue;
+
+ if (tArea == ctArea)
+ {
+ m_isBattlefront = true;
+ return;
+ }
+
+ // build path between these two spawn points - assume if path fails, it at least got close
+ // (ie: imagine spawn points that you jump down from - can't path to)
+ CNavArea *goalArea = NULL;
+ NavAreaBuildPath( tArea, ctArea, NULL, cost, &goalArea );
+
+ if (goalArea == NULL)
+ continue;
+
+
+/**
+ * @todo Need to enumerate ALL paths between all pairs of spawn points to find all battlefront areas
+ */
+
+ // find the area with the earliest overlapping occupy times
+ CNavArea *battlefront = NULL;
+ float earliestTime = 999999.9f;
+
+ const float epsilon = 1.0f;
+ CNavArea *area;
+ for( area = goalArea; area; area = area->GetParent() )
+ {
+ if (fabs(area->GetEarliestOccupyTime( TEAM_TERRORIST ) - area->GetEarliestOccupyTime( TEAM_CT )) < epsilon)
+ {
+ }
+
+ }
+ }
+ }
+#endif
+#endif
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the filename for this map's "nav map" file
+ */
+const char *CNavMesh::GetFilename( void ) const
+{
+ // filename is local to game dir for Steam, so we need to prepend game dir for regular file save
+ char gamePath[256];
+ engine->GetGameDir( gamePath, 256 );
+
+ // persistant return value
+ static char filename[256];
+ Q_snprintf( filename, sizeof( filename ), "%s\\" FORMAT_NAVFILE, gamePath, STRING( gpGlobals->mapname ) );
+
+ return filename;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/*
+============
+COM_FixSlashes
+
+Changes all '/' characters into '\' characters, in place.
+============
+*/
+inline void COM_FixSlashes( char *pname )
+{
+#ifdef _WIN32
+ while ( *pname )
+ {
+ if ( *pname == '/' )
+ *pname = '\\';
+ pname++;
+ }
+#else
+ while ( *pname )
+ {
+ if ( *pname == '\\' )
+ *pname = '/';
+ pname++;
+ }
+#endif
+}
+
+static void WarnIfMeshNeedsAnalysis( int version )
+{
+ // Quick check to warn about needing to analyze: nav_strip, nav_delete, etc set
+ // every CNavArea's m_approachCount to 0, and delete their m_spotEncounterList.
+ // So, if no area has either, odds are good we need an analyze.
+
+ if ( version >= 14 )
+ {
+ if ( !TheNavMesh->IsAnalyzed() )
+ {
+ Warning( "The nav mesh needs a full nav_analyze\n" );
+ return;
+ }
+ }
+#ifdef CSTRIKE_DLL
+ else
+ {
+ bool hasApproachAreas = false;
+ bool hasSpotEncounters = false;
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CCSNavArea *area = dynamic_cast< CCSNavArea * >( TheNavAreas[ it ] );
+ if ( area )
+ {
+ if ( area->GetApproachInfoCount() )
+ {
+ hasApproachAreas = true;
+ }
+
+ if ( area->GetSpotEncounterCount() )
+ {
+ hasSpotEncounters = true;
+ }
+ }
+ }
+
+ if ( !hasApproachAreas || !hasSpotEncounters )
+ {
+ Warning( "The nav mesh needs a full nav_analyze\n" );
+ }
+ }
+#endif
+}
+
+/**
+ * Store Navigation Mesh to a file
+ */
+bool CNavMesh::Save( void ) const
+{
+ WarnIfMeshNeedsAnalysis( NavCurrentVersion );
+
+ const char *filename = GetFilename();
+ if (filename == NULL)
+ return false;
+
+ //
+ // Store the NAV file
+ //
+ COM_FixSlashes( const_cast<char *>(filename) );
+
+ // get size of source bsp file for later (before we open the nav file for writing, in
+ // case of failure)
+ char *bspFilename = GetBspFilename( filename );
+ if (bspFilename == NULL)
+ {
+ return false;
+ }
+
+ CUtlBuffer fileBuffer( 4096, 1024*1024 );
+
+ // store "magic number" to help identify this kind of file
+ unsigned int magic = NAV_MAGIC_NUMBER;
+ fileBuffer.PutUnsignedInt( magic );
+
+ // store version number of file
+ // 1 = hiding spots as plain vector array
+ // 2 = hiding spots as HidingSpot objects
+ // 3 = Encounter spots use HidingSpot ID's instead of storing vector again
+ // 4 = Includes size of source bsp file to verify nav data correlation
+ // ---- Beta Release at V4 -----
+ // 5 = Added Place info
+ // ---- Conversion to Src ------
+ // 6 = Added Ladder info
+ // 7 = Areas store ladder ID's so ladders can have one-way connections
+ // 8 = Added earliest occupy times (2 floats) to each area
+ // 9 = Promoted CNavArea's attribute flags to a short
+ // 10 - Added sub-version number to allow derived classes to have custom area data
+ // 11 - Added light intensity to each area
+ // 12 - Storing presence of unnamed areas in the PlaceDirectory
+ // 13 - Widened NavArea attribute bits from unsigned short to int
+ // 14 - Added a bool for if the nav needs analysis
+ // 15 - removed approach areas
+ // 16 - Added visibility data to the base mesh
+ fileBuffer.PutUnsignedInt( NavCurrentVersion );
+
+ // The sub-version number is maintained and owned by classes derived from CNavMesh and CNavArea
+ // and allows them to track their custom data just as we do at this top level
+ fileBuffer.PutUnsignedInt( GetSubVersionNumber() );
+
+ // store the size of source bsp file in the nav file
+ // so we can test if the bsp changed since the nav file was made
+ unsigned int bspSize = filesystem->Size( bspFilename );
+ DevMsg( "Size of bsp file '%s' is %u bytes.\n", bspFilename, bspSize );
+
+ fileBuffer.PutUnsignedInt( bspSize );
+
+ // Store the analysis state
+ fileBuffer.PutUnsignedChar( m_isAnalyzed );
+
+ //
+ // Build a directory of the Places in this map
+ //
+ placeDirectory.Reset();
+
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ CNavArea *area = TheNavAreas[ nit ];
+
+ Place place = area->GetPlace();
+ placeDirectory.AddPlace( place );
+ }
+
+ placeDirectory.Save( fileBuffer );
+
+ SaveCustomDataPreArea( fileBuffer );
+
+ //
+ // Store navigation areas
+ //
+ {
+ // store number of areas
+ unsigned int count = TheNavAreas.Count();
+ fileBuffer.PutUnsignedInt( count );
+
+ // store each area
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ area->Save( fileBuffer, NavCurrentVersion );
+ }
+ }
+
+ //
+ // Store ladders
+ //
+ {
+ // store number of ladders
+ unsigned int count = m_ladders.Count();
+ fileBuffer.PutUnsignedInt( count );
+
+ // store each ladder
+ for ( int i=0; i<m_ladders.Count(); ++i )
+ {
+ CNavLadder *ladder = m_ladders[i];
+ ladder->Save( fileBuffer, NavCurrentVersion );
+ }
+ }
+
+ //
+ // Store derived class mesh info
+ //
+ SaveCustomData( fileBuffer );
+
+ if ( !filesystem->WriteFile( filename, "MOD", fileBuffer ) )
+ {
+ Warning( "Unable to save %d bytes to %s\n", fileBuffer.Size(), filename );
+ return false;
+ }
+
+ unsigned int navSize = filesystem->Size( filename );
+ DevMsg( "Size of nav file '%s' is %u bytes.\n", filename, navSize );
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+static NavErrorType CheckNavFile( const char *bspFilename )
+{
+ if ( !bspFilename )
+ return NAV_CANT_ACCESS_FILE;
+
+ char baseName[256];
+ Q_StripExtension(bspFilename,baseName,sizeof(baseName));
+ char bspPathname[256];
+ Q_snprintf(bspPathname,sizeof(bspPathname), FORMAT_BSPFILE, baseName);
+ char filename[256];
+ Q_snprintf(filename,sizeof(filename), FORMAT_NAVFILE, baseName);
+
+ bool navIsInBsp = false;
+ FileHandle_t file = filesystem->Open( filename, "rb", "MOD" ); // this ignores .nav files embedded in the .bsp ...
+ if ( !file )
+ {
+ navIsInBsp = true;
+ file = filesystem->Open( filename, "rb", "GAME" ); // ... and this looks for one if it's the only one around.
+ }
+
+ if (!file)
+ {
+ return NAV_CANT_ACCESS_FILE;
+ }
+
+ // check magic number
+ int result;
+ unsigned int magic;
+ result = filesystem->Read( &magic, sizeof(unsigned int), file );
+ if (!result || magic != NAV_MAGIC_NUMBER)
+ {
+ filesystem->Close( file );
+ return NAV_INVALID_FILE;
+ }
+
+ // read file version number
+ unsigned int version;
+ result = filesystem->Read( &version, sizeof(unsigned int), file );
+ if (!result || version > NavCurrentVersion || version < 4)
+ {
+ filesystem->Close( file );
+ return NAV_BAD_FILE_VERSION;
+ }
+
+ // get size of source bsp file and verify that the bsp hasn't changed
+ unsigned int saveBspSize;
+ filesystem->Read( &saveBspSize, sizeof(unsigned int), file );
+
+ // verify size
+ unsigned int bspSize = filesystem->Size( bspPathname );
+
+ if (bspSize != saveBspSize && !navIsInBsp)
+ {
+ return NAV_FILE_OUT_OF_DATE;
+ }
+
+ return NAV_OK;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CommandNavCheckFileConsistency( void )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ FileFindHandle_t findHandle;
+ const char *bspFilename = filesystem->FindFirstEx( "maps/*.bsp", "MOD", &findHandle );
+ while ( bspFilename )
+ {
+ switch ( CheckNavFile( bspFilename ) )
+ {
+ case NAV_CANT_ACCESS_FILE:
+ Warning( "Missing nav file for %s\n", bspFilename );
+ break;
+ case NAV_INVALID_FILE:
+ Warning( "Invalid nav file for %s\n", bspFilename );
+ break;
+ case NAV_BAD_FILE_VERSION:
+ Warning( "Old nav file for %s\n", bspFilename );
+ break;
+ case NAV_FILE_OUT_OF_DATE:
+ Warning( "The nav file for %s is built from an old version of the map\n", bspFilename );
+ break;
+ case NAV_OK:
+ Msg( "The nav file for %s is up-to-date\n", bspFilename );
+ break;
+ }
+
+ bspFilename = filesystem->FindNext( findHandle );
+ }
+ filesystem->FindClose( findHandle );
+}
+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 );
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reads the used place names from the nav file (can be used to selectively precache before the nav is loaded)
+ */
+const CUtlVector< Place > *CNavMesh::GetPlacesFromNavFile( bool *hasUnnamedPlaces )
+{
+ placeDirectory.Reset();
+ // nav filename is derived from map filename
+ char filename[256];
+ Q_snprintf( filename, sizeof( filename ), FORMAT_NAVFILE, STRING( gpGlobals->mapname ) );
+
+ CUtlBuffer fileBuffer( 4096, 1024*1024, CUtlBuffer::READ_ONLY );
+ if ( !filesystem->ReadFile( filename, "GAME", fileBuffer ) ) // this ignores .nav files embedded in the .bsp ...
+ {
+ if ( !filesystem->ReadFile( filename, "BSP", fileBuffer ) ) // ... and this looks for one if it's the only one around.
+ {
+ return NULL;
+ }
+ }
+
+ if ( IsX360() )
+ {
+ // 360 has compressed NAVs
+ CLZMA lzma;
+ if ( lzma.IsCompressed( (unsigned char *)fileBuffer.Base() ) )
+ {
+ int originalSize = lzma.GetActualSize( (unsigned char *)fileBuffer.Base() );
+ unsigned char *pOriginalData = new unsigned char[originalSize];
+ lzma.Uncompress( (unsigned char *)fileBuffer.Base(), pOriginalData );
+ fileBuffer.AssumeMemory( pOriginalData, originalSize, originalSize, CUtlBuffer::READ_ONLY );
+ }
+ }
+
+ // check magic number
+ unsigned int magic = fileBuffer.GetUnsignedInt();
+ if ( !fileBuffer.IsValid() || magic != NAV_MAGIC_NUMBER )
+ {
+ return NULL; // Corrupt nav file?
+ }
+
+ // read file version number
+ unsigned int version = fileBuffer.GetUnsignedInt();
+ if ( !fileBuffer.IsValid() || version > NavCurrentVersion )
+ {
+ return NULL; // Unknown nav file version
+ }
+
+ if ( version < 5 )
+ {
+ return NULL; // Too old to have place names
+ }
+
+ unsigned int subVersion = 0;
+ if ( version >= 10 )
+ {
+ subVersion = fileBuffer.GetUnsignedInt();
+ if ( !fileBuffer.IsValid() )
+ {
+ return NULL; // No sub-version
+ }
+ }
+
+ fileBuffer.GetUnsignedInt(); // skip BSP file size
+ if ( version >= 14 )
+ {
+ fileBuffer.GetUnsignedChar(); // skip m_isAnalyzed
+ }
+
+ placeDirectory.Load( fileBuffer, version );
+
+ LoadCustomDataPreArea( fileBuffer, subVersion );
+
+ if ( hasUnnamedPlaces )
+ {
+ *hasUnnamedPlaces = placeDirectory.HasUnnamedPlaces();
+ }
+
+ return placeDirectory.GetPlaces();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Load AI navigation data from a file
+ */
+NavErrorType CNavMesh::Load( void )
+{
+ MDLCACHE_CRITICAL_SECTION();
+
+ // free previous navigation mesh data
+ Reset();
+ placeDirectory.Reset();
+ CNavVectorNoEditAllocator::Reset();
+
+ GameRules()->OnNavMeshLoad();
+
+ CNavArea::m_nextID = 1;
+
+ // nav filename is derived from map filename
+ char filename[256];
+ Q_snprintf( filename, sizeof( filename ), FORMAT_NAVFILE, STRING( gpGlobals->mapname ) );
+
+ bool navIsInBsp = false;
+ CUtlBuffer fileBuffer( 4096, 1024*1024, CUtlBuffer::READ_ONLY );
+ if ( !filesystem->ReadFile( filename, "MOD", fileBuffer ) ) // this ignores .nav files embedded in the .bsp ...
+ {
+ navIsInBsp = true;
+ if ( !filesystem->ReadFile( filename, "BSP", fileBuffer ) ) // ... and this looks for one if it's the only one around.
+ {
+ return NAV_CANT_ACCESS_FILE;
+ }
+ }
+
+ if ( IsX360() )
+ {
+ // 360 has compressed NAVs
+ CLZMA lzma;
+ if ( lzma.IsCompressed( (unsigned char *)fileBuffer.Base() ) )
+ {
+ int originalSize = lzma.GetActualSize( (unsigned char *)fileBuffer.Base() );
+ unsigned char *pOriginalData = new unsigned char[originalSize];
+ lzma.Uncompress( (unsigned char *)fileBuffer.Base(), pOriginalData );
+ fileBuffer.AssumeMemory( pOriginalData, originalSize, originalSize, CUtlBuffer::READ_ONLY );
+ }
+ }
+
+ // check magic number
+ unsigned int magic = fileBuffer.GetUnsignedInt();
+ if ( !fileBuffer.IsValid() || magic != NAV_MAGIC_NUMBER )
+ {
+ Msg( "Invalid navigation file '%s'.\n", filename );
+ return NAV_INVALID_FILE;
+ }
+
+ // read file version number
+ unsigned int version = fileBuffer.GetUnsignedInt();
+ if ( !fileBuffer.IsValid() || version > NavCurrentVersion )
+ {
+ Msg( "Unknown navigation file version.\n" );
+ return NAV_BAD_FILE_VERSION;
+ }
+
+ unsigned int subVersion = 0;
+ if ( version >= 10 )
+ {
+ subVersion = fileBuffer.GetUnsignedInt();
+ if ( !fileBuffer.IsValid() )
+ {
+ Msg( "Error reading sub-version number.\n" );
+ return NAV_INVALID_FILE;
+ }
+ }
+
+ if ( version >= 4 )
+ {
+ // get size of source bsp file and verify that the bsp hasn't changed
+ unsigned int saveBspSize = fileBuffer.GetUnsignedInt();
+
+ // verify size
+ char *bspFilename = GetBspFilename( filename );
+ if ( bspFilename == NULL )
+ {
+ return NAV_INVALID_FILE;
+ }
+
+ unsigned int bspSize = filesystem->Size( bspFilename );
+
+ if ( bspSize != saveBspSize && !navIsInBsp )
+ {
+ if ( engine->IsDedicatedServer() )
+ {
+ // Warning doesn't print to the dedicated server console, so we'll use Msg instead
+ DevMsg( "The Navigation Mesh was built using a different version of this map.\n" );
+ }
+ else
+ {
+ DevWarning( "The Navigation Mesh was built using a different version of this map.\n" );
+ }
+ m_isOutOfDate = true;
+ }
+ }
+
+ if ( version >= 14 )
+ {
+ m_isAnalyzed = fileBuffer.GetUnsignedChar() != 0;
+ }
+ else
+ {
+ m_isAnalyzed = false;
+ }
+
+ // load Place directory
+ if ( version >= 5 )
+ {
+ placeDirectory.Load( fileBuffer, version );
+ }
+
+ LoadCustomDataPreArea( fileBuffer, subVersion );
+
+ // get number of areas
+ unsigned int count = fileBuffer.GetUnsignedInt();
+ unsigned int i;
+
+ if ( count == 0 )
+ {
+ return NAV_INVALID_FILE;
+ }
+
+ Extent extent;
+ extent.lo.x = 9999999999.9f;
+ extent.lo.y = 9999999999.9f;
+ extent.hi.x = -9999999999.9f;
+ extent.hi.y = -9999999999.9f;
+
+ // load the areas and compute total extent
+ TheNavMesh->PreLoadAreas( count );
+ Extent areaExtent;
+ for( i=0; i<count; ++i )
+ {
+ CNavArea *area = TheNavMesh->CreateArea();
+ area->Load( fileBuffer, version, subVersion );
+ TheNavAreas.AddToTail( area );
+
+ area->GetExtent( &areaExtent );
+
+ if (areaExtent.lo.x < extent.lo.x)
+ extent.lo.x = areaExtent.lo.x;
+ if (areaExtent.lo.y < extent.lo.y)
+ extent.lo.y = areaExtent.lo.y;
+ if (areaExtent.hi.x > extent.hi.x)
+ extent.hi.x = areaExtent.hi.x;
+ if (areaExtent.hi.y > extent.hi.y)
+ extent.hi.y = areaExtent.hi.y;
+ }
+
+ // add the areas to the grid
+ AllocateGrid( extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y );
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ AddNavArea( TheNavAreas[ it ] );
+ }
+
+
+ //
+ // Set up all the ladders
+ //
+ if (version >= 6)
+ {
+ count = fileBuffer.GetUnsignedInt();
+ m_ladders.EnsureCapacity( count );
+
+ // load the ladders
+ for( i=0; i<count; ++i )
+ {
+ CNavLadder *ladder = new CNavLadder;
+ ladder->Load( fileBuffer, version );
+ m_ladders.AddToTail( ladder );
+ }
+ }
+ else
+ {
+ BuildLadders();
+ }
+
+ // mark stairways (TODO: this can be removed once all maps are re-saved with this attribute in them)
+ MarkStairAreas();
+
+ //
+ // Load derived class mesh info
+ //
+ LoadCustomData( fileBuffer, subVersion );
+
+ //
+ // Bind pointers, etc
+ //
+ NavErrorType loadResult = PostLoad( version );
+
+ WarnIfMeshNeedsAnalysis( version );
+
+ return loadResult;
+}
+
+
+struct OneWayLink_t
+{
+ CNavArea *destArea;
+ CNavArea *area;
+ int backD;
+
+ static int Compare(const OneWayLink_t *lhs, const OneWayLink_t *rhs )
+ {
+ int result = ( lhs->destArea - rhs->destArea );
+ if ( result != 0 )
+ {
+ return result;
+ }
+ return ( lhs->backD - rhs->backD );
+ }
+};
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked after all areas have been loaded - for pointer binding, etc
+ */
+NavErrorType CNavMesh::PostLoad( unsigned int version )
+{
+ // allow areas to connect to each other, etc
+ FOR_EACH_VEC( TheNavAreas, pit )
+ {
+ CNavArea *area = TheNavAreas[ pit ];
+ area->PostLoad();
+ }
+
+ // allow hiding spots to compute information
+ FOR_EACH_VEC( TheHidingSpots, hit )
+ {
+ HidingSpot *spot = TheHidingSpots[ hit ];
+ spot->PostLoad();
+ }
+
+ if ( version < 8 )
+ {
+ // Old nav meshes need to compute earliest occupy times
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ CNavArea *area = TheNavAreas[ nit ];
+ area->ComputeEarliestOccupyTimes();
+ }
+ }
+
+ ComputeBattlefrontAreas();
+
+ //
+ // Allow each nav area to know what other areas have one-way connections to it. Need to gather
+ // then sort due to allocation restrictions on the 360
+ //
+
+
+ OneWayLink_t oneWayLink;
+ CUtlVectorFixedGrowable<OneWayLink_t, 512> oneWayLinks;
+
+ FOR_EACH_VEC( TheNavAreas, oit )
+ {
+ oneWayLink.area = TheNavAreas[ oit ];
+
+ for( int d=0; d<NUM_DIRECTIONS; d++ )
+ {
+ const NavConnectVector *connectList = oneWayLink.area->GetAdjacentAreas( (NavDirType)d );
+
+ FOR_EACH_VEC( (*connectList), it )
+ {
+ NavConnect connect = (*connectList)[ it ];
+ oneWayLink.destArea = connect.area;
+
+ // if the area we connect to has no connection back to us, allow that area to remember us as an incoming connection
+ oneWayLink.backD = OppositeDirection( (NavDirType)d );
+ const NavConnectVector *backConnectList = oneWayLink.destArea->GetAdjacentAreas( (NavDirType)oneWayLink.backD );
+ bool isOneWay = true;
+ FOR_EACH_VEC( (*backConnectList), bit )
+ {
+ NavConnect backConnect = (*backConnectList)[ bit ];
+ if (backConnect.area->GetID() == oneWayLink.area->GetID())
+ {
+ isOneWay = false;
+ break;
+ }
+ }
+
+ if (isOneWay)
+ {
+ oneWayLinks.AddToTail( oneWayLink );
+ }
+ }
+ }
+ }
+
+ oneWayLinks.Sort( &OneWayLink_t::Compare );
+
+ for ( int i = 0; i < oneWayLinks.Count(); i++ )
+ {
+ // add this one-way connection
+ oneWayLinks[i].destArea->AddIncomingConnection( oneWayLinks[i].area, (NavDirType)oneWayLinks[i].backD );
+ }
+
+ ValidateNavAreaConnections();
+
+ // TERROR: loading into a map directly creates entities before the mesh is loaded. Tell the preexisting
+ // entities now that the mesh is loaded so they can update areas.
+ for ( int i=0; i<m_avoidanceObstacles.Count(); ++i )
+ {
+ m_avoidanceObstacles[i]->OnNavMeshLoaded();
+ }
+
+ // the Navigation Mesh has been successfully loaded
+ m_isLoaded = true;
+
+ return NAV_OK;
+}
|