//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// nav_file.cpp
// Reading and writing nav files
// Author: Michael S. Booth (mike@turtlerockstudios.com), 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;
}