|
|
//===== 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<CUtlString> 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<CUtlString> 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 <substring>\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
|