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.
1365 lines
35 KiB
1365 lines
35 KiB
//========= Copyright © 1996-2005, 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"
|
|
|
|
#ifdef CSTRIKE_DLL
|
|
#include "cs_shareddefs.h"
|
|
#include "cs_nav_pathfind.h"
|
|
#endif
|
|
|
|
#if 0
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/// The current version of the nav file format
|
|
const int NavCurrentVersion = 9;
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
//
|
|
// 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.
|
|
//
|
|
class PlaceDirectory
|
|
{
|
|
public:
|
|
|
|
typedef unsigned short IndexType;
|
|
|
|
void Reset( void )
|
|
{
|
|
m_directory.RemoveAll();
|
|
}
|
|
|
|
/// return true if this place is already in the directory
|
|
bool IsKnown( Place place ) const
|
|
{
|
|
return m_directory.HasElement( place );
|
|
}
|
|
|
|
/// return the directory index corresponding to this Place (0 = no entry)
|
|
IndexType GetIndex( Place place ) const
|
|
{
|
|
if (place == UNDEFINED_PLACE)
|
|
return 0;
|
|
|
|
int i = m_directory.Find( place );
|
|
|
|
if (i < 0)
|
|
{
|
|
Assert( false && "PlaceDirectory::GetIndex failure" );
|
|
return 0;
|
|
}
|
|
|
|
return (IndexType)(i+1);
|
|
}
|
|
|
|
/// add the place to the directory if not already known
|
|
void AddPlace( Place place )
|
|
{
|
|
if (place == UNDEFINED_PLACE)
|
|
return;
|
|
|
|
Assert( place < 1000 );
|
|
|
|
if (IsKnown( place ))
|
|
return;
|
|
|
|
m_directory.AddToTail( place );
|
|
}
|
|
|
|
/// given an index, return the Place
|
|
Place IndexToPlace( IndexType entry ) const
|
|
{
|
|
if (entry == 0)
|
|
return UNDEFINED_PLACE;
|
|
|
|
int i = entry-1;
|
|
|
|
if (i >= m_directory.Count())
|
|
{
|
|
Assert( false && "PlaceDirectory::IndexToPlace: Invalid entry" );
|
|
return UNDEFINED_PLACE;
|
|
}
|
|
|
|
return m_directory[ i ];
|
|
}
|
|
|
|
/// store the directory
|
|
void Save( FileHandle_t file )
|
|
{
|
|
// store number of entries in directory
|
|
IndexType count = (IndexType)m_directory.Count();
|
|
filesystem->Write( &count, sizeof(IndexType), file );
|
|
|
|
// 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);
|
|
filesystem->Write( &len, sizeof(unsigned short), file );
|
|
filesystem->Write( placeName, len, file );
|
|
}
|
|
}
|
|
|
|
/// load the directory
|
|
void Load( FileHandle_t file )
|
|
{
|
|
// read number of entries
|
|
IndexType count;
|
|
filesystem->Read( &count, sizeof(IndexType), file );
|
|
|
|
m_directory.RemoveAll();
|
|
|
|
// read each entry
|
|
char placeName[256];
|
|
unsigned short len;
|
|
for( int i=0; i<count; ++i )
|
|
{
|
|
filesystem->Read( &len, sizeof(unsigned short), file );
|
|
filesystem->Read( placeName, len, file );
|
|
|
|
AddPlace( TheNavMesh->NameToPlace( placeName ) );
|
|
}
|
|
}
|
|
|
|
private:
|
|
CUtlVector< Place > m_directory;
|
|
};
|
|
|
|
static PlaceDirectory placeDirectory;
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Replace extension with "bsp"
|
|
*/
|
|
char *GetBspFilename( const char *navFilename )
|
|
{
|
|
static char bspFilename[256];
|
|
|
|
Q_snprintf( bspFilename, sizeof( bspFilename ), "maps\\%s.bsp", 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;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/*
|
|
void CNavArea::Save( FILE *fp ) const
|
|
{
|
|
fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.lo.y, m_extent.lo.z );
|
|
fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.lo.y, m_neZ );
|
|
fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.hi.y, m_extent.hi.z );
|
|
fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.hi.y, m_swZ );
|
|
|
|
static int base = 1;
|
|
fprintf( fp, "\n\ng %04dArea%s%s%s%s\n", m_id,
|
|
(GetAttributes() & BOT_NAV_CROUCH) ? "CROUCH" : "",
|
|
(GetAttributes() & BOT_NAV_JUMP) ? "JUMP" : "",
|
|
(GetAttributes() & BOT_NAV_PRECISE) ? "PRECISE" : "",
|
|
(GetAttributes() & BOT_NAV_NO_JUMP) ? "NO_JUMP" : "" );
|
|
fprintf( fp, "f %d %d %d %d\n\n", base, base+1, base+2, base+3 );
|
|
base += 4;
|
|
}
|
|
*/
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Save a navigation area to the opened binary stream
|
|
*/
|
|
void CNavArea::Save( FileHandle_t file, unsigned int version ) const
|
|
{
|
|
// save ID
|
|
filesystem->Write( &m_id, sizeof(unsigned int), file );
|
|
|
|
// save attribute flags
|
|
filesystem->Write( &m_attributeFlags, sizeof(unsigned short), file );
|
|
|
|
// save extent of area
|
|
filesystem->Write( &m_extent, 6*sizeof(float), file );
|
|
|
|
// save heights of implicit corners
|
|
filesystem->Write( &m_neZ, sizeof(float), file );
|
|
filesystem->Write( &m_swZ, sizeof(float), file );
|
|
|
|
// 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();
|
|
filesystem->Write( &count, sizeof(unsigned int), file );
|
|
|
|
FOR_EACH_LL( m_connect[d], it )
|
|
{
|
|
NavConnect connect = m_connect[d][ it ];
|
|
filesystem->Write( &connect.area->m_id, sizeof(unsigned int), file );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Store hiding spots for this area
|
|
//
|
|
unsigned char count;
|
|
if (m_hidingSpotList.Count() > 255)
|
|
{
|
|
count = 255;
|
|
Warning( "Warning: NavArea #%d: Truncated hiding spot list to 255\n", m_id );
|
|
}
|
|
else
|
|
{
|
|
count = (unsigned char)m_hidingSpotList.Count();
|
|
}
|
|
filesystem->Write( &count, sizeof(unsigned char), file );
|
|
|
|
// store HidingSpot objects
|
|
unsigned int saveCount = 0;
|
|
FOR_EACH_LL( m_hidingSpotList, hit )
|
|
{
|
|
HidingSpot *spot = m_hidingSpotList[ hit ];
|
|
|
|
spot->Save( file, version );
|
|
|
|
// overflow check
|
|
if (++saveCount == count)
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Save the approach areas for this area
|
|
//
|
|
|
|
// save number of approach areas
|
|
filesystem->Write( &m_approachCount, sizeof(unsigned char), file );
|
|
|
|
// save approach area info
|
|
unsigned char type;
|
|
unsigned int zero = 0;
|
|
for( int a=0; a<m_approachCount; ++a )
|
|
{
|
|
if (m_approach[a].here.area)
|
|
filesystem->Write( &m_approach[a].here.area->m_id, sizeof(unsigned int), file );
|
|
else
|
|
filesystem->Write( &zero, sizeof(unsigned int), file );
|
|
|
|
if (m_approach[a].prev.area)
|
|
filesystem->Write( &m_approach[a].prev.area->m_id, sizeof(unsigned int), file );
|
|
else
|
|
filesystem->Write( &zero, sizeof(unsigned int), file );
|
|
type = (unsigned char)m_approach[a].prevToHereHow;
|
|
filesystem->Write( &type, sizeof(unsigned char), file );
|
|
|
|
if (m_approach[a].next.area)
|
|
filesystem->Write( &m_approach[a].next.area->m_id, sizeof(unsigned int), file );
|
|
else
|
|
filesystem->Write( &zero, sizeof(unsigned int), file );
|
|
type = (unsigned char)m_approach[a].hereToNextHow;
|
|
filesystem->Write( &type, sizeof(unsigned char), file );
|
|
}
|
|
|
|
//
|
|
// Save encounter spots for this area
|
|
//
|
|
{
|
|
// save number of encounter paths for this area
|
|
unsigned int count = m_spotEncounterList.Count();
|
|
filesystem->Write( &count, sizeof(unsigned int), file );
|
|
|
|
SpotEncounter *e;
|
|
FOR_EACH_LL( m_spotEncounterList, it )
|
|
{
|
|
e = m_spotEncounterList[ it ];
|
|
|
|
if (e->from.area)
|
|
filesystem->Write( &e->from.area->m_id, sizeof(unsigned int), file );
|
|
else
|
|
filesystem->Write( &zero, sizeof(unsigned int), file );
|
|
|
|
unsigned char dir = (unsigned char)e->fromDir;
|
|
filesystem->Write( &dir, sizeof(unsigned char), file );
|
|
|
|
if (e->to.area)
|
|
filesystem->Write( &e->to.area->m_id, sizeof(unsigned int), file );
|
|
else
|
|
filesystem->Write( &zero, sizeof(unsigned int), file );
|
|
|
|
dir = (unsigned char)e->toDir;
|
|
filesystem->Write( &dir, sizeof(unsigned char), file );
|
|
|
|
// write list of spots along this path
|
|
unsigned char spotCount;
|
|
if (e->spotList.Count() > 255)
|
|
{
|
|
spotCount = 255;
|
|
Warning( "Warning: NavArea #%d: Truncated encounter spot list to 255\n", m_id );
|
|
}
|
|
else
|
|
{
|
|
spotCount = (unsigned char)e->spotList.Count();
|
|
}
|
|
filesystem->Write( &spotCount, sizeof(unsigned char), file );
|
|
|
|
saveCount = 0;
|
|
FOR_EACH_LL( e->spotList, sit )
|
|
{
|
|
SpotOrder *order = &e->spotList[ 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;
|
|
filesystem->Write( &id, sizeof(unsigned int), file );
|
|
|
|
unsigned char t = (unsigned char)(255 * order->t);
|
|
filesystem->Write( &t, sizeof(unsigned char), file );
|
|
|
|
// overflow check
|
|
if (++saveCount == spotCount)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// store place dictionary entry
|
|
PlaceDirectory::IndexType entry = placeDirectory.GetIndex( GetPlace() );
|
|
filesystem->Write( &entry, sizeof(entry), file );
|
|
|
|
// write out ladder info
|
|
int i;
|
|
for ( i=0; i<CSNavLadder::NUM_LADDER_DIRECTIONS; ++i )
|
|
{
|
|
// save number of encounter paths for this area
|
|
unsigned int count = m_ladder[i].Count();
|
|
filesystem->Write( &count, sizeof(unsigned int), file );
|
|
|
|
NavLadderConnect ladder;
|
|
FOR_EACH_LL( m_ladder[i], it )
|
|
{
|
|
ladder = m_ladder[i][it];
|
|
|
|
unsigned int id = ladder.ladder->GetID();
|
|
filesystem->Write( &id, sizeof( id ), file );
|
|
}
|
|
}
|
|
|
|
// save earliest occupy times
|
|
for( i=0; i<MAX_NAV_TEAMS; ++i )
|
|
{
|
|
// no spot in the map should take longer than this to reach
|
|
filesystem->Write( &m_earliestOccupyTime[i], sizeof(m_earliestOccupyTime[i]), file );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Load a navigation area from the file
|
|
*/
|
|
void CNavArea::Load( FileHandle_t file, unsigned int version )
|
|
{
|
|
// load ID
|
|
filesystem->Read( &m_id, sizeof(unsigned int), file );
|
|
|
|
// update nextID to avoid collisions
|
|
if (m_id >= m_nextID)
|
|
m_nextID = m_id+1;
|
|
|
|
// load attribute flags
|
|
if ( version <= 8 )
|
|
{
|
|
unsigned char flags = 0;
|
|
filesystem->Read( &flags, sizeof(unsigned char), file );
|
|
m_attributeFlags = flags;
|
|
}
|
|
else
|
|
{
|
|
filesystem->Read( &m_attributeFlags, sizeof(unsigned short), file );
|
|
}
|
|
|
|
// load extent of area
|
|
filesystem->Read( &m_extent, 6*sizeof(float), file );
|
|
|
|
m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f;
|
|
m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f;
|
|
m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f;
|
|
|
|
// load heights of implicit corners
|
|
filesystem->Read( &m_neZ, sizeof(float), file );
|
|
filesystem->Read( &m_swZ, sizeof(float), file );
|
|
|
|
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;
|
|
int result = filesystem->Read( &count, sizeof(unsigned int), file );
|
|
Assert( result == sizeof(unsigned int) );
|
|
|
|
for( unsigned int i=0; i<count; ++i )
|
|
{
|
|
NavConnect connect;
|
|
result = filesystem->Read( &connect.id, sizeof(unsigned int), file );
|
|
Assert( result == sizeof(unsigned int) );
|
|
|
|
// 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;
|
|
filesystem->Read( &hidingSpotCount, sizeof(unsigned char), file );
|
|
|
|
if (version == 1)
|
|
{
|
|
// load simple vector array
|
|
Vector pos;
|
|
for( int h=0; h<hidingSpotCount; ++h )
|
|
{
|
|
filesystem->Read( &pos, 3 * sizeof(float), file );
|
|
|
|
// create new hiding spot and put on master list
|
|
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
|
|
spot->SetPosition( pos );
|
|
spot->SetFlags( HidingSpot::IN_COVER );
|
|
m_hidingSpotList.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( file, version );
|
|
|
|
m_hidingSpotList.AddToTail( spot );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Load number of approach areas
|
|
//
|
|
filesystem->Read( &m_approachCount, sizeof(unsigned char), file );
|
|
|
|
// load approach area info (IDs)
|
|
unsigned char type;
|
|
for( int a=0; a<m_approachCount; ++a )
|
|
{
|
|
filesystem->Read( &m_approach[a].here.id, sizeof(unsigned int), file );
|
|
|
|
filesystem->Read( &m_approach[a].prev.id, sizeof(unsigned int), file );
|
|
filesystem->Read( &type, sizeof(unsigned char), file );
|
|
m_approach[a].prevToHereHow = (NavTraverseType)type;
|
|
|
|
filesystem->Read( &m_approach[a].next.id, sizeof(unsigned int), file );
|
|
filesystem->Read( &type, sizeof(unsigned char), file );
|
|
m_approach[a].hereToNextHow = (NavTraverseType)type;
|
|
}
|
|
|
|
|
|
//
|
|
// Load encounter paths for this area
|
|
//
|
|
unsigned int count;
|
|
filesystem->Read( &count, sizeof(unsigned int), file );
|
|
|
|
if (version < 3)
|
|
{
|
|
// old data, read and discard
|
|
for( unsigned int e=0; e<count; ++e )
|
|
{
|
|
SpotEncounter encounter;
|
|
|
|
filesystem->Read( &encounter.from.id, sizeof(unsigned int), file );
|
|
filesystem->Read( &encounter.to.id, sizeof(unsigned int), file );
|
|
|
|
filesystem->Read( &encounter.path.from.x, 3 * sizeof(float), file );
|
|
filesystem->Read( &encounter.path.to.x, 3 * sizeof(float), file );
|
|
|
|
// read list of spots along this path
|
|
unsigned char spotCount;
|
|
filesystem->Read( &spotCount, sizeof(unsigned char), file );
|
|
|
|
for( int s=0; s<spotCount; ++s )
|
|
{
|
|
Vector pos;
|
|
filesystem->Read( &pos, 3*sizeof(float), file );
|
|
filesystem->Read( &pos, sizeof(float), file );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
for( unsigned int e=0; e<count; ++e )
|
|
{
|
|
SpotEncounter *encounter = new SpotEncounter;
|
|
|
|
filesystem->Read( &encounter->from.id, sizeof(unsigned int), file );
|
|
|
|
unsigned char dir;
|
|
filesystem->Read( &dir, sizeof(unsigned char), file );
|
|
encounter->fromDir = static_cast<NavDirType>( dir );
|
|
|
|
filesystem->Read( &encounter->to.id, sizeof(unsigned int), file );
|
|
|
|
filesystem->Read( &dir, sizeof(unsigned char), file );
|
|
encounter->toDir = static_cast<NavDirType>( dir );
|
|
|
|
// read list of spots along this path
|
|
unsigned char spotCount;
|
|
filesystem->Read( &spotCount, sizeof(unsigned char), file );
|
|
|
|
SpotOrder order;
|
|
for( int s=0; s<spotCount; ++s )
|
|
{
|
|
filesystem->Read( &order.id, sizeof(unsigned int), file );
|
|
|
|
unsigned char t;
|
|
filesystem->Read( &t, sizeof(unsigned char), file );
|
|
|
|
order.t = (float)t/255.0f;
|
|
|
|
encounter->spotList.AddToTail( order );
|
|
}
|
|
|
|
m_spotEncounterList.AddToTail( encounter );
|
|
}
|
|
|
|
if (version < 5)
|
|
return;
|
|
|
|
//
|
|
// Load Place data
|
|
//
|
|
PlaceDirectory::IndexType entry;
|
|
filesystem->Read( &entry, sizeof(entry), file );
|
|
|
|
// convert entry to actual Place
|
|
SetPlace( placeDirectory.IndexToPlace( entry ) );
|
|
|
|
if ( version < 7 )
|
|
return;
|
|
|
|
// load ladder data
|
|
for ( int dir=0; dir<CSNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
|
|
{
|
|
filesystem->Read( &count, sizeof(unsigned int), file );
|
|
{
|
|
for( unsigned int i=0; i<count; ++i )
|
|
{
|
|
NavLadderConnect connect;
|
|
filesystem->Read( &connect.id, sizeof(unsigned int), file );
|
|
|
|
bool alreadyConnected = false;
|
|
FOR_EACH_LL( 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;
|
|
|
|
// 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
|
|
filesystem->Read( &m_earliestOccupyTime[i], sizeof(m_earliestOccupyTime[i]), file );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* 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 ];
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// resolve approach area IDs
|
|
for( int a=0; a<m_approachCount; ++a )
|
|
{
|
|
m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
|
|
if (m_approach[a].here.id && m_approach[a].here.area == NULL)
|
|
{
|
|
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
|
|
error = NAV_CORRUPT_DATA;
|
|
}
|
|
|
|
m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
|
|
if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
|
|
{
|
|
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
|
|
error = NAV_CORRUPT_DATA;
|
|
}
|
|
|
|
m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
|
|
if (m_approach[a].next.id && m_approach[a].next.area == NULL)
|
|
{
|
|
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
|
|
error = NAV_CORRUPT_DATA;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// build overlap list
|
|
/// @todo Optimize this
|
|
FOR_EACH_VEC( TheNavAreas, nit )
|
|
{
|
|
CNavArea *area = TheNavAreas[ nit ];
|
|
|
|
if (area == this)
|
|
continue;
|
|
|
|
if (IsOverlapping( area ))
|
|
m_overlappingAreas.AddToTail( area );
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* 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 );
|
|
|
|
static char filename[256];
|
|
Q_snprintf( filename, sizeof( filename ), "%s\\maps\\%s.nav", 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( void )
|
|
{
|
|
// 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.
|
|
{
|
|
bool hasApproachAreas = false;
|
|
bool hasSpotEncounters = false;
|
|
|
|
FOR_EACH_VEC( TheNavAreas, it )
|
|
{
|
|
CNavArea *area = TheNavAreas[ it ];
|
|
if ( area->GetApproachInfoCount() )
|
|
{
|
|
hasApproachAreas = true;
|
|
}
|
|
|
|
if ( area->GetSpotEncounterCount() )
|
|
{
|
|
hasSpotEncounters = true;
|
|
}
|
|
}
|
|
|
|
if ( !hasApproachAreas || !hasSpotEncounters )
|
|
{
|
|
Warning( "The nav mesh needs a full nav_analyze\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store Navigation Mesh to a file
|
|
*/
|
|
bool CNavMesh::Save( void ) const
|
|
{
|
|
WarnIfMeshNeedsAnalysis();
|
|
|
|
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;
|
|
}
|
|
|
|
FileHandle_t file = filesystem->Open( filename, "wb" );
|
|
|
|
if (!file)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// store "magic number" to help identify this kind of file
|
|
unsigned int magic = NAV_MAGIC_NUMBER;
|
|
filesystem->Write( &magic, sizeof(unsigned int), file );
|
|
|
|
// 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
|
|
unsigned int version = NavCurrentVersion;
|
|
filesystem->Write( &version, sizeof(unsigned int), file );
|
|
|
|
// 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 );
|
|
|
|
filesystem->Write( &bspSize, sizeof(unsigned int), file );
|
|
|
|
|
|
//
|
|
// Build a directory of the Places in this map
|
|
//
|
|
placeDirectory.Reset();
|
|
|
|
FOR_EACH_VEC( TheNavAreas, nit )
|
|
{
|
|
CNavArea *area = TheNavAreas[ nit ];
|
|
|
|
Place place = area->GetPlace();
|
|
|
|
if (place)
|
|
{
|
|
placeDirectory.AddPlace( place );
|
|
}
|
|
}
|
|
|
|
placeDirectory.Save( file );
|
|
|
|
|
|
//
|
|
// Store navigation areas
|
|
//
|
|
{
|
|
// store number of areas
|
|
unsigned int count = TheNavAreas.Count();
|
|
filesystem->Write( &count, sizeof(unsigned int), file );
|
|
|
|
// store each area
|
|
FOR_EACH_VEC( TheNavAreas, it )
|
|
{
|
|
CNavArea *area = TheNavAreas[ it ];
|
|
|
|
area->Save( file, version );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Store ladders
|
|
//
|
|
{
|
|
// store number of ladders
|
|
unsigned int count = m_ladders.Count();
|
|
filesystem->Write( &count, sizeof(unsigned int), file );
|
|
|
|
// store each ladder
|
|
FOR_EACH_VEC( m_ladders, it )
|
|
{
|
|
CNavLadder *ladder = m_ladders[ it ];
|
|
|
|
ladder->Save( file, version );
|
|
}
|
|
}
|
|
|
|
filesystem->Flush( file );
|
|
filesystem->Close( file );
|
|
|
|
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 bspPathname[256];
|
|
char filename[256];
|
|
Q_strncpy( bspPathname, "maps/", sizeof( bspPathname ) );
|
|
Q_strncat( bspPathname, bspFilename, sizeof( bspPathname ), COPY_ALL_CHARACTERS );
|
|
Q_strncpy( filename, bspPathname, sizeof( filename ) );
|
|
Q_SetExtension( filename, ".nav", sizeof( filename ) );
|
|
|
|
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 );
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Load AI navigation data from a file
|
|
*/
|
|
NavErrorType CNavMesh::Load( void )
|
|
{
|
|
// free previous navigation mesh data
|
|
Reset();
|
|
placeDirectory.Reset();
|
|
|
|
CNavArea::m_nextID = 1;
|
|
|
|
// nav filename is derived from map filename
|
|
char filename[256];
|
|
Q_snprintf( filename, sizeof( filename ), "maps\\%s.nav", STRING( gpGlobals->mapname ) );
|
|
|
|
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)
|
|
{
|
|
Msg( "Invalid navigation file '%s'.\n", filename );
|
|
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)
|
|
{
|
|
Msg( "Unknown navigation file version.\n" );
|
|
filesystem->Close( file );
|
|
return NAV_BAD_FILE_VERSION;
|
|
}
|
|
|
|
if (version >= 4)
|
|
{
|
|
// 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
|
|
char *bspFilename = GetBspFilename( filename );
|
|
if ( bspFilename == NULL )
|
|
{
|
|
filesystem->Close( file );
|
|
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
|
|
Msg( "The Navigation Mesh was built using a different version of this map.\n" );
|
|
}
|
|
else
|
|
{
|
|
Warning( "The Navigation Mesh was built using a different version of this map.\n" );
|
|
}
|
|
m_isFromCurrentMap = false;
|
|
}
|
|
}
|
|
|
|
// load Place directory
|
|
if (version >= 5)
|
|
{
|
|
placeDirectory.Load( file );
|
|
}
|
|
|
|
// get number of areas
|
|
unsigned int count;
|
|
unsigned int i;
|
|
result = filesystem->Read( &count, sizeof(unsigned int), 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
|
|
for( i=0; i<count; ++i )
|
|
{
|
|
CNavArea *area = new CNavArea;
|
|
area->Load( file, version );
|
|
TheNavAreas.AddToTail( area );
|
|
|
|
Extent areaExtent;
|
|
area->GetExtent(&areaExtent);
|
|
|
|
// check validity of nav area
|
|
if (areaExtent.lo.x >= areaExtent.hi.x || areaExtent.lo.y >= areaExtent.hi.y)
|
|
Warning( "WARNING: Degenerate Navigation Area #%d at ( %g, %g, %g )\n",
|
|
area->GetID(), area->m_center.x, area->m_center.y, area->m_center.z );
|
|
|
|
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)
|
|
{
|
|
result = filesystem->Read( &count, sizeof(unsigned int), file );
|
|
|
|
// load the ladders
|
|
for( i=0; i<count; ++i )
|
|
{
|
|
CNavLadder *ladder = new CNavLadder;
|
|
ladder->Load( file, version );
|
|
m_ladders.AddToTail( ladder );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BuildLadders();
|
|
}
|
|
|
|
// 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();
|
|
|
|
// the Navigation Mesh has been successfully loaded
|
|
m_isLoaded = true;
|
|
|
|
filesystem->Close( file );
|
|
|
|
WarnIfMeshNeedsAnalysis();
|
|
|
|
return NAV_OK;
|
|
}
|
|
#endif
|