//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ // //===========================================================================// #include "quakedef.h" #include "bspfile.h" #include "host.h" #include "sys.h" #include "filesystem_engine.h" #include "utldict.h" #include "demo.h" #include "tier2/fileutils.h" #ifndef DEDICATED #include "vgui_baseui_interface.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // Imported from other .cpp files void Host_Map_f( const CCommand &args ); void Host_MapGroup_f( const CCommand &args ); void Host_SplitScreen_Map_f( const CCommand &args ); void Host_Map_Background_f( const CCommand &args ); void Host_Map_Commentary_f( const CCommand &args ); void Host_Changelevel_f( const CCommand &args ); void Host_Changelevel2_f( const CCommand &args ); ConVar host_maplist_recurse_subdirs( "host_maplist_recurse_subdirs", "1" ); //----------------------------------------------------------------------------- // Purpose: For each map, stores when the map last changed on disk and whether // it is a valid map //----------------------------------------------------------------------------- class CMapListItem { public: enum { INVALID = 0, PENDING, VALID, }; CMapListItem( void ); void SetValid( int valid ); int GetValid( void ) const; void SetFileTimestamp( long ts ); long GetFileTimestamp( void ) const; bool IsSameTime( long ts ) const; static long GetFSTimeStamp( char const *name ); static int CheckFSHeaderVersion( char const *name ); private: int m_nValid; long m_lFileTimestamp; }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMapListItem::CMapListItem( void ) { m_nValid = PENDING; m_lFileTimestamp = 0L; } //----------------------------------------------------------------------------- // Purpose: // Input : valid - //----------------------------------------------------------------------------- void CMapListItem::SetValid( int valid ) { m_nValid = valid; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- int CMapListItem::GetValid( void ) const { return m_nValid; } //----------------------------------------------------------------------------- // Purpose: // Input : ts - //----------------------------------------------------------------------------- void CMapListItem::SetFileTimestamp( long ts ) { m_lFileTimestamp = ts; } //----------------------------------------------------------------------------- // Purpose: // Output : long //----------------------------------------------------------------------------- long CMapListItem::GetFileTimestamp( void ) const { return m_lFileTimestamp; } //----------------------------------------------------------------------------- // Purpose: Check whether this map file has changed related to the passed in timestamp // Input : ts - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CMapListItem::IsSameTime( long ts ) const { return ( m_lFileTimestamp == ts ) ? true : false; } //----------------------------------------------------------------------------- // Purpose: Get the timestamp for the file from the file system // Input : *name - // Output : long //----------------------------------------------------------------------------- long CMapListItem::GetFSTimeStamp( char const *name ) { long ts = g_pFileSystem->GetFileTime( name ); return ts; } //----------------------------------------------------------------------------- // Purpose: Check whether the specified map header version is up-to-date // Input : *name - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- int CMapListItem::CheckFSHeaderVersion( char const *name ) { BSPHeader_t header; memset( &header, 0, sizeof( header ) ); FileHandle_t fp = g_pFileSystem->Open ( name, "rb" ); if ( fp ) { g_pFileSystem->Read( &header, sizeof( header ), fp ); g_pFileSystem->Close( fp ); } return ( header.m_nVersion >= MINBSPVERSION && header.m_nVersion <= BSPVERSION ) ? VALID : INVALID; } // How often to check the filesystem for updated map info #define MIN_REFRESH_INTERVAL 60.0f //----------------------------------------------------------------------------- // Purpose: Stores the current list of maps for the engine //----------------------------------------------------------------------------- class CMapListManager { public: CMapListManager( void ); ~CMapListManager( void ); // See if it's time to revisit the items in the list void RefreshList( void ); // Get item count, etc int GetMapCount( void ) const; int IsMapValid( int index ) const; char const *GetMapName( int index ) const; void Think( void ); private: // Clear list void ClearList( void ); // Rebuild list from scratch void BuildList( void ); private: // Dictionary of items CUtlDict< CMapListItem, int > m_Items; // Time of last update float m_flLastRefreshTime; bool m_bDirty; }; // Singleton manager object static CMapListManager g_MapListMgr; void Host_UpdateMapList( void ) { g_MapListMgr.Think(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMapListManager::CMapListManager( void ) { m_flLastRefreshTime = -1.0f; m_bDirty = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMapListManager::~CMapListManager( void ) { ClearList(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapListManager::Think( void ) { return; if ( !m_bDirty ) return; #ifndef DEDICATED // Only update pending files if console is visible to avoid slamming FS while in a map if ( !EngineVGui()->IsConsoleVisible() ) return; #endif int i; m_bDirty = false; for ( i = m_Items.Count() - 1; i >= 0 ; i-- ) { CMapListItem *item = &m_Items[ i ]; if ( item->GetValid() != CMapListItem::PENDING ) { continue; } char const *filename = m_Items.GetElementName( i ); item->SetValid( CMapListItem::CheckFSHeaderVersion( filename ) ); // Keep fixing things up next frame m_bDirty = true; break; } } //----------------------------------------------------------------------------- // Purpose: FIXME: Refresh doesn't notice maps that have been deleted... oh well //----------------------------------------------------------------------------- void CMapListManager::RefreshList( void ) { if ( m_flLastRefreshTime == -1.0f ) { BuildList(); return; } if ( realtime < m_flLastRefreshTime + MIN_REFRESH_INTERVAL ) return; ConDMsg( "Refreshing map list...\n" ); if ( host_maplist_recurse_subdirs.GetBool() ) { char mapwild[MAX_QPATH]; Q_snprintf( mapwild, sizeof( mapwild ), "*.%sbsp", IsX360() ? "360." : "" ); CUtlVector outList; RecursiveFindFilesMatchingName( &outList, "maps", mapwild, "GAME" ); FOR_EACH_VEC( outList, i ) { const char* curMap = outList[i].Access(); int idx = m_Items.Find( curMap ); if ( idx == m_Items.InvalidIndex() ) { CMapListItem item; item.SetFileTimestamp( item.GetFSTimeStamp( curMap ) ); item.SetValid( CMapListItem::PENDING ); // Insert into dictionary m_Items.Insert( curMap, item ); m_bDirty = true; } else { CMapListItem *item = &m_Items[ idx ]; Assert( item ); // Make sure data is up to date long timestamp = g_pFileSystem->GetFileTime( curMap ); if ( !item->IsSameTime( timestamp ) ) { item->SetFileTimestamp( timestamp ); item->SetValid( CMapListItem::PENDING ); m_bDirty = true; } } } } else { // Search the directory structure. char mapwild[MAX_QPATH]; Q_strncpy(mapwild,"maps/*.bsp", sizeof( mapwild ) ); char const *findfn = Sys_FindFirst( mapwild, NULL, 0 ); while ( findfn ) { if ( IsPC() && V_stristr( findfn, ".360.bsp" ) ) { // ignore 360 bsp findfn = Sys_FindNext( NULL, 0 ); continue; } else if ( IsX360() && !V_stristr( findfn, ".360.bsp" ) ) { // ignore pc bsp findfn = Sys_FindNext( NULL, 0 ); continue; } char sz[ MAX_QPATH ]; Q_snprintf( sz, sizeof( sz ), "maps/%s", findfn ); int idx = m_Items.Find( sz ); if ( idx == m_Items.InvalidIndex() ) { CMapListItem item; item.SetFileTimestamp( item.GetFSTimeStamp( sz ) ); item.SetValid( CMapListItem::PENDING ); // Insert into dictionary m_Items.Insert( sz, item ); m_bDirty = true; } else { CMapListItem *item = &m_Items[ idx ]; Assert( item ); // Make sure data is up to date long timestamp = g_pFileSystem->GetFileTime( sz ); if ( !item->IsSameTime( timestamp ) ) { item->SetFileTimestamp( timestamp ); item->SetValid( CMapListItem::PENDING ); m_bDirty = true; } } findfn = Sys_FindNext( NULL, 0 ); } Sys_FindClose(); } m_flLastRefreshTime = realtime; } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CMapListManager::GetMapCount( void ) const { return m_Items.Count(); } //----------------------------------------------------------------------------- // Purpose: // Input : index - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- int CMapListManager::IsMapValid( int index ) const { if ( !m_Items.IsValidIndex( index ) ) return false; CMapListItem const *item = &m_Items[ index ]; Assert( item ); return item->GetValid(); } //----------------------------------------------------------------------------- // Purpose: // Input : index - // Output : char const //----------------------------------------------------------------------------- char const *CMapListManager::GetMapName( int index ) const { if ( !m_Items.IsValidIndex( index ) ) return "Invalid!!!"; return m_Items.GetElementName( index ); } //----------------------------------------------------------------------------- // Purpose: Wipe the list //----------------------------------------------------------------------------- void CMapListManager::ClearList( void ) { m_Items.Purge(); m_bDirty = false; } //----------------------------------------------------------------------------- // Purpose: Rebuild the entire list //----------------------------------------------------------------------------- void CMapListManager::BuildList( void ) { ClearList(); if ( host_maplist_recurse_subdirs.GetBool() ) { char mapwild[MAX_QPATH]; Q_snprintf( mapwild, sizeof( mapwild ), "*.%sbsp", IsX360() ? "360." : "" ); CUtlVector outList; RecursiveFindFilesMatchingName( &outList, "maps", mapwild, "GAME" ); FOR_EACH_VEC( outList, i ) { const char* curMap = outList[i].Access(); if ( IsPC() && V_stristr( curMap, ".360.bsp" ) ) { // ignore 360 bsp continue; } else if ( IsX360() && !V_stristr( curMap, ".360.bsp" ) ) { // ignore pc bsp continue; } CMapListItem item; item.SetFileTimestamp( item.GetFSTimeStamp( curMap ) ); item.SetValid( CMapListItem::PENDING ); // Insert into dictionary int idx = m_Items.Find( curMap ); if ( idx == m_Items.InvalidIndex() ) { m_Items.Insert( curMap, item ); } } } else { // Search the directory structure. char mapwild[MAX_QPATH]; Q_snprintf( mapwild, sizeof( mapwild ), "maps/*.%sbsp", IsX360() ? "360." : "" ); char const *findfn = Sys_FindFirst( mapwild, NULL, 0 ); while ( findfn ) { if ( IsPC() && V_stristr( findfn, ".360.bsp" ) ) { // ignore 360 bsp findfn = Sys_FindNext( NULL, 0 ); continue; } else if ( IsX360() && !V_stristr( findfn, ".360.bsp" ) ) { // ignore pc bsp findfn = Sys_FindNext( NULL, 0 ); continue; } char sz[ MAX_QPATH ]; Q_snprintf( sz, sizeof( sz ), "maps/%s", findfn ); CMapListItem item; item.SetFileTimestamp( item.GetFSTimeStamp( sz ) ); item.SetValid( CMapListItem::PENDING ); // Insert into dictionary int idx = m_Items.Find( sz ); if ( idx == m_Items.InvalidIndex() ) { m_Items.Insert( sz, item ); } findfn = Sys_FindNext( NULL, 0 ); } Sys_FindClose(); } // Remember time we build the list m_flLastRefreshTime = realtime; m_bDirty = true; } //----------------------------------------------------------------------------- // Purpose: // Input : *pakorfilesys - // *mapname - // Output : static void //----------------------------------------------------------------------------- static bool MapList_CheckPrintMap( const char *pakorfilesys, const char *mapname, int valid, bool showoutdated, bool verbose ) { bool validorpending = ( valid != CMapListItem::INVALID ) ? true : false; if ( !verbose ) { return validorpending; } char prefix[ 32 ]; prefix[ 0 ] = 0; switch ( valid ) { default: case CMapListItem::VALID: break; case CMapListItem::PENDING: Q_strncpy( prefix, "PENDING: ", sizeof( prefix ) ); break; case CMapListItem::INVALID: Q_strncpy( prefix, "OUTDATED: ", sizeof( prefix ) ); break; } if ( validorpending ^ showoutdated ) { ConMsg( "%s %s %s\n", prefix, pakorfilesys, mapname ); } return validorpending; } //----------------------------------------------------------------------------- // Purpose: // Input : *pszSubString - // listobsolete - // maxitemlength - // Output : static int //----------------------------------------------------------------------------- static int MapList_CountMaps( const char *pszSubString, bool listobsolete, int& maxitemlength ) { g_MapListMgr.RefreshList(); maxitemlength = 0; int substringlength = 0; if ( pszSubString && pszSubString[0] ) { substringlength = strlen(pszSubString); } // // search through the path, one element at a time // int count = 0; int showOutdated; for( showOutdated = listobsolete ? 1 : 0; showOutdated >= 0; showOutdated-- ) { for ( int i = 0; i < g_MapListMgr.GetMapCount(); i++ ) { char const *mapname = g_MapListMgr.GetMapName( i ); int valid = g_MapListMgr.IsMapValid( i ); if ( !substringlength || ( V_stristr( &mapname[ 5 ], pszSubString ) != NULL ) ) { if ( MapList_CheckPrintMap( "(fs)", &mapname[ 5 ], valid, showOutdated ? true : false, false ) ) { maxitemlength = MAX( maxitemlength, (int)( strlen( &mapname[ 5 ] ) + 1 ) ); count++; } } } } return count; } //----------------------------------------------------------------------------- // Purpose: // Lists all maps matching the substring // If the substring is empty, or "*", then lists all maps // Input : *pszSubString - //----------------------------------------------------------------------------- static int MapList_ListMaps( const char *pszSubString, bool listobsolete, bool verbose, int maxcount, int maxitemlength, char maplist[][ 64 ] ) { int substringlength = 0; if (pszSubString && pszSubString[0]) { substringlength = strlen(pszSubString); } // // search through the path, one element at a time // if ( verbose ) { ConMsg( "-------------\n"); } int count = 0; int showOutdated; for( showOutdated = listobsolete ? 1 : 0; showOutdated >= 0; showOutdated-- ) { if ( count >= maxcount ) break; //search the directory structure. for ( int i = 0; i < g_MapListMgr.GetMapCount(); i++ ) { if ( count >= maxcount ) break; char const *mapname = g_MapListMgr.GetMapName( i ); int valid = g_MapListMgr.IsMapValid( i ); if ( !substringlength || ( V_stristr( &mapname[ 5 ], pszSubString ) != NULL ) ) { if ( MapList_CheckPrintMap( "(fs)", &mapname[ 5 ], valid, showOutdated ? true : false, verbose ) ) { if ( maxitemlength != 0 ) { Q_strncpy( maplist[ count ], &mapname[ 5 ], maxitemlength ); } count++; } } } } return count; } //----------------------------------------------------------------------------- // Purpose: // Input : *partial - // context - // longest - // maxcommands - // **commands - // Output : int //----------------------------------------------------------------------------- int _Host_Map_f_CompletionFunc( char const *cmdname, char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { char *substring = (char *)partial; if ( Q_strstr( partial, cmdname ) ) { substring = (char *)partial + strlen( cmdname ); } int longest = 0; int count = MIN( MapList_CountMaps( substring, false, longest ), COMMAND_COMPLETION_MAXITEMS ); if ( count > 0 ) { MapList_ListMaps( substring, false, false, COMMAND_COMPLETION_MAXITEMS, longest, commands ); // Now prepend maps * in front of all of the options int i; for ( i = 0; i < count ; i++ ) { char old[ COMMAND_COMPLETION_ITEM_LENGTH ]; Q_strncpy( old, commands[ i ], sizeof( old ) ); Q_snprintf( commands[ i ], sizeof( commands[ i ] ), "%s%s", cmdname, old ); commands[ i ][ strlen( commands[ i ] ) - 4 ] = 0; } } return count; } //----------------------------------------------------------------------------- // Purpose: // Input : *partial - // context - // longest - // maxcommands - // **commands - // Output : int //----------------------------------------------------------------------------- static int Host_SSMap_f_CompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { char const *cmdname = "ss_map "; return _Host_Map_f_CompletionFunc( cmdname, partial, commands ); } //----------------------------------------------------------------------------- // Purpose: // Input : *partial - // context - // longest - // maxcommands - // **commands - // Output : int //----------------------------------------------------------------------------- static int Host_Map_f_CompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { char const *cmdname = "map "; return _Host_Map_f_CompletionFunc( cmdname, partial, commands ); } //----------------------------------------------------------------------------- // Purpose: // Input : *partial - // context - // longest - // maxcommands - // **commands - // Output : int //----------------------------------------------------------------------------- static int Host_Background_f_CompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { char const *cmdname = "map_background "; return _Host_Map_f_CompletionFunc( cmdname, partial, commands ); } //----------------------------------------------------------------------------- // Purpose: // Input : *partial - // context - // longest - // maxcommands - // **commands - // Output : int //----------------------------------------------------------------------------- static int Host_Map_Commentary_f_CompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { char const *cmdname = "map_commentary "; return _Host_Map_f_CompletionFunc( cmdname, partial, commands ); } //----------------------------------------------------------------------------- // Purpose: // Input : *partial - // context - // longest - // maxcommands - // **commands - // Output : int //----------------------------------------------------------------------------- static int Host_Changelevel_f_CompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { char const *cmdname = "changelevel "; return _Host_Map_f_CompletionFunc( cmdname, partial, commands ); } //----------------------------------------------------------------------------- // Purpose: // Input : *partial - // context - // longest - // maxcommands - // **commands - // Output : int //----------------------------------------------------------------------------- static int Host_Changelevel2_f_CompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { char const *cmdname = "changelevel2 "; return _Host_Map_f_CompletionFunc( cmdname, partial, commands ); } //----------------------------------------------------------------------------- // Purpose: do a dir of the maps dir //----------------------------------------------------------------------------- static void Host_Maps_f( const CCommand &args ) { const char *pszSubString = NULL; if ( args.ArgC() != 2 && args.ArgC() != 3 ) { ConMsg( "Usage: maps \nmaps * for full listing\n" ); return; } if ( args.ArgC() == 2 ) { pszSubString = args[1]; if (!pszSubString || !pszSubString[0]) return; } if ( pszSubString && ( pszSubString[0] == '*' )) pszSubString = NULL; int longest = 0; int count = MapList_CountMaps( pszSubString, true, longest ); if ( count > 0 ) { MapList_ListMaps( pszSubString, true, true, count, 0, NULL ); } } #ifndef BENCHMARK static ConCommand maps("maps", Host_Maps_f, "Displays list of maps." ); static ConCommand map("map", Host_Map_f, "Start playing on specified map.", FCVAR_DONTRECORD, Host_Map_f_CompletionFunc ); static ConCommand mapgroup( "mapgroup", Host_MapGroup_f, "Specify a map group", FCVAR_DONTRECORD ); static ConCommand ss_map("ss_map", Host_SplitScreen_Map_f, "Start playing on specified map with max allowed splitscreen players.", FCVAR_DONTRECORD, Host_SSMap_f_CompletionFunc ); static ConCommand map_background("map_background", Host_Map_Background_f, "Runs a map as the background to the main menu.", FCVAR_DONTRECORD, Host_Background_f_CompletionFunc ); static ConCommand map_commentary("map_commentary", Host_Map_Commentary_f, "Start playing, with commentary, on a specified map.", FCVAR_DONTRECORD, Host_Map_Commentary_f_CompletionFunc ); static ConCommand changelevel("changelevel", Host_Changelevel_f, "Change server to the specified map", FCVAR_DONTRECORD, Host_Changelevel_f_CompletionFunc ); static ConCommand changelevel2("changelevel2", Host_Changelevel2_f, "Transition to the specified map in single player", FCVAR_DONTRECORD, Host_Changelevel2_f_CompletionFunc ); #endif