//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "BonusMapsDatabase.h" #include "EngineInterface.h" #include "tier1/convar.h" #include "tier1/utlbuffer.h" #include "filesystem.h" #include "ModInfo.h" #include "EngineInterface.h" #include "ixboxsystem.h" #include "KeyValues.h" #include "BasePanel.h" #include "GameUI_Interface.h" #include "BonusMapsDialog.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define MOD_DIR ( IsXbox() ? "DEFAULT_WRITE_PATH" : "MOD" ) const char g_pszMedalNames[4][8] = { "none", "bronze", "silver", "gold" }; const char *COM_GetModDirectory(); bool WriteBonusMapSavedData( KeyValues *data ) { if ( IsX360() && ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED ) ) return false; CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); data->RecursiveSaveToFile( buf, 0 ); char szFilename[_MAX_PATH]; if ( IsX360() ) Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/bonus_maps_data.bmd" ); else Q_snprintf( szFilename, sizeof( szFilename ), "save/bonus_maps_data.bmd" ); bool bWriteSuccess = g_pFullFileSystem->WriteFile( szFilename, MOD_DIR, buf ); xboxsystem->FinishContainerWrites(); return bWriteSuccess; } void GetBooleanStatus( KeyValues *pBonusFilesKey, BonusMapDescription_t &map ) { KeyValues *pFileKey = NULL; KeyValues *pBonusKey = NULL; for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() ) { if ( Q_strcmp( pFileKey->GetName(), map.szFileName ) == 0 ) { for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() ) { if ( Q_strcmp( pBonusKey->GetName(), map.szMapName ) == 0 ) { // Found the data break; } } break; } } if ( pBonusKey ) { map.bLocked = ( pBonusKey->GetInt( "lock" ) != 0 ); map.bComplete = ( pBonusKey->GetInt( "complete" ) != 0 ); } } bool SetBooleanStatus( KeyValues *pBonusFilesKey, const char *pchName, const char *pchFileName, const char *pchMapName, bool bValue ) { // Don't create entries for files that don't exist if ( !IsX360() && ! (g_pFullFileSystem->FileExists( pchFileName, "MOD" ) || g_pFullFileSystem->IsDirectory( pchFileName, "MOD" ) ) ) { DevMsg( "Failed to set boolean status for file %s.", pchFileName ); return false; } bool bChanged = false; KeyValues *pFileKey = NULL; KeyValues *pBonusKey = NULL; for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() ) { if ( Q_strcmp( pFileKey->GetName(), pchFileName ) == 0 ) { for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() ) { if ( Q_strcmp( pBonusKey->GetName(), pchMapName ) == 0 ) { // Found the data break; } } break; } } if ( !pFileKey ) { // Didn't find it, so create a new spot for the data pFileKey = new KeyValues( pchFileName ); pBonusFilesKey->AddSubKey( pFileKey ); } if ( !pBonusKey ) { pBonusKey = new KeyValues( pchMapName, pchName, "0" ); pFileKey->AddSubKey( pBonusKey ); bChanged = true; } if ( ( pBonusKey->GetInt( pchName ) != 0 ) != bValue ) { bChanged = true; pBonusKey->SetInt( pchName, bValue ); } return bChanged; } float GetChallengeBests( KeyValues *pBonusFilesKey, BonusMapDescription_t &challenge ) { // There's no challenges, so bail and assume 0% challenge completion if ( challenge.m_pChallenges == NULL || challenge.m_pChallenges->Count() == 0 ) return 0.0f; KeyValues *pFileKey = NULL; KeyValues *pBonusKey = NULL; for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() ) { if ( Q_strcmp( pFileKey->GetName(), challenge.szFileName ) == 0 ) { for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() ) { if ( Q_strcmp( pBonusKey->GetName(), challenge.szMapName ) == 0 ) { // Found the data break; } } break; } } float fChallengePoints = 0.0f; for ( int iChallenge = 0; iChallenge < challenge.m_pChallenges->Count(); ++iChallenge ) { ChallengeDescription_t *pChallengeDescription = &((*challenge.m_pChallenges)[ iChallenge ]); pChallengeDescription->iBest = ( ( pBonusKey ) ? ( pBonusKey->GetInt( pChallengeDescription->szName, -1 ) ) : ( -1 ) ); if ( pChallengeDescription->iBest >= 0 ) { if ( pChallengeDescription->iBest <= pChallengeDescription->iGold ) fChallengePoints += 3.0f; else if ( pChallengeDescription->iBest <= pChallengeDescription->iSilver ) fChallengePoints += 2.0f; else if ( pChallengeDescription->iBest <= pChallengeDescription->iBronze ) fChallengePoints += 1.0f; } } return fChallengePoints / ( challenge.m_pChallenges->Count() * 3.0f ); } bool UpdateChallengeBest( KeyValues *pBonusFilesKey, const BonusMapChallenge_t &challenge ) { // Don't create entries for files that don't exist if ( !IsX360() && !g_pFullFileSystem->FileExists( challenge.szFileName, "MOD" ) ) { DevMsg( "Failed to set challenge best for file %s.", challenge.szFileName ); return false; } bool bChanged = false; KeyValues *pFileKey = NULL; KeyValues *pBonusKey = NULL; for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() ) { if ( Q_strcmp( pFileKey->GetName(), challenge.szFileName ) == 0 ) { for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() ) { if ( Q_strcmp( pBonusKey->GetName(), challenge.szMapName ) == 0 ) { // Found the challenge break; } } break; } } if ( !pFileKey ) { // Didn't find it, so create a new spot for data pFileKey = new KeyValues( challenge.szFileName ); pBonusFilesKey->AddSubKey( pFileKey ); } if ( !pBonusKey ) { pBonusKey = new KeyValues( challenge.szMapName, challenge.szChallengeName, -1 ); pFileKey->AddSubKey( pBonusKey ); bChanged = true; } int iCurrentBest = pBonusKey->GetInt( challenge.szChallengeName, -1 ); if ( iCurrentBest == -1 || iCurrentBest > challenge.iBest ) { bChanged = true; pBonusKey->SetInt( challenge.szChallengeName, challenge.iBest ); } return bChanged; } void GetChallengeMedals( ChallengeDescription_t *pChallengeDescription, int &iBest, int &iEarnedMedal, int &iNext, int &iNextMedal ) { iBest = pChallengeDescription->iBest; if ( iBest == -1 ) iEarnedMedal = 0; else if ( iBest <= pChallengeDescription->iGold ) iEarnedMedal = 3; else if ( iBest <= pChallengeDescription->iSilver ) iEarnedMedal = 2; else if ( iBest <= pChallengeDescription->iBronze ) iEarnedMedal = 1; else iEarnedMedal = 0; iNext = -1; switch ( iEarnedMedal ) { case 0: iNext = pChallengeDescription->iBronze; iNextMedal = 1; break; case 1: iNext = pChallengeDescription->iSilver; iNextMedal = 2; break; case 2: iNext = pChallengeDescription->iGold; iNextMedal = 3; break; case 3: iNext = -1; iNextMedal = -1; break; } } CBonusMapsDatabase *g_pBonusMapsDatabase = NULL; CBonusMapsDatabase *BonusMapsDatabase( void ) { if ( !g_pBonusMapsDatabase ) static CBonusMapsDatabase StaticBonusMapsDatabase; return g_pBonusMapsDatabase; } //----------------------------------------------------------------------------- // Purpose:Constructor //----------------------------------------------------------------------------- CBonusMapsDatabase::CBonusMapsDatabase( void ) { Assert( g_pBonusMapsDatabase == NULL ); // There should only be 1 bonus maps database g_pBonusMapsDatabase = this; RootPath(); m_pBonusMapsManifest = new KeyValues( "bonus_maps_manifest" ); m_pBonusMapsManifest->LoadFromFile( g_pFullFileSystem, "scripts/bonus_maps_manifest.txt", NULL ); m_iX360BonusesUnlocked = -1; // Only used on X360 m_bHasLoadedSaveData = false; ReadBonusMapSaveData(); } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CBonusMapsDatabase::~CBonusMapsDatabase() { g_pBonusMapsDatabase = NULL; } extern bool g_bIsCreatingNewGameMenuForPreFetching; bool CBonusMapsDatabase::ReadBonusMapSaveData( void ) { if ( !m_pBonusMapSavedData ) m_pBonusMapSavedData = new KeyValues( "bonus_map_saved_data" ); if ( g_bIsCreatingNewGameMenuForPreFetching ) { // Although we may have a storage device it's not going to be able to find our file at this point! BAIL! return false; } #ifdef _X360 // Nothing to read if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED ) return false; #endif char szFilename[_MAX_PATH]; if ( IsX360() ) Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/bonus_maps_data.bmd" ); else Q_snprintf( szFilename, sizeof( szFilename ), "save/bonus_maps_data.bmd" ); m_pBonusMapSavedData->LoadFromFile( g_pFullFileSystem, szFilename, NULL ); m_bSavedDataChanged = false; m_bHasLoadedSaveData = true; return true; } bool CBonusMapsDatabase::WriteSaveData( void ) { bool bSuccess = false; if ( m_bSavedDataChanged ) bSuccess = WriteBonusMapSavedData( m_pBonusMapSavedData ); if ( bSuccess ) m_bSavedDataChanged = false; return bSuccess; } void CBonusMapsDatabase::RootPath( void ) { m_iDirDepth = 0; V_strcpy_safe( m_szCurrentPath, "." ); } void CBonusMapsDatabase::AppendPath( const char *pchAppend ) { ++m_iDirDepth; char szCurPathTmp[MAX_PATH]; V_strcpy_safe( szCurPathTmp, m_szCurrentPath ); Q_snprintf( m_szCurrentPath, sizeof( m_szCurrentPath ), "%s/%s", szCurPathTmp, pchAppend ); } void CBonusMapsDatabase::BackPath( void ) { if ( m_iDirDepth == 0 ) return; if ( m_iDirDepth == 1 ) { RootPath(); // back to root return; } --m_iDirDepth; Q_strrchr( m_szCurrentPath, '/' )[ 0 ] = '\0'; // remove a dir from the end } void CBonusMapsDatabase::SetPath( const char *pchPath, int iDirDepth ) { V_strcpy_safe( m_szCurrentPath, pchPath ); m_iDirDepth = iDirDepth; } void CBonusMapsDatabase::ClearBonusMapsList( void ) { m_BonusMaps.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: builds bonus map list from directory //----------------------------------------------------------------------------- void CBonusMapsDatabase::ScanBonusMaps( void ) { // Don't load in the bonus maps before we've properly read in the save data if ( !m_bHasLoadedSaveData ) { if ( ! ReadBonusMapSaveData() ) return; } char *pCurrentPath = &(m_szCurrentPath[2]); // Reset completion percentage m_iCompletableLevels = 0; m_fCurrentCompletion = 0.0f; // populate list box with all bonus maps in the current path char szDirectory[_MAX_PATH]; if ( Q_strcmp( m_szCurrentPath, "." ) == 0 ) { // We're at the root, so look at the directories in the manifest KeyValues *pKey = NULL; for ( pKey = m_pBonusMapsManifest->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() ) { const char *pchType = pKey->GetName(); if ( Q_strcmp( pchType, "search" ) == 0 ) { // Search through the directory Q_snprintf( szDirectory, sizeof( szDirectory ), "%s/", pKey->GetString() ); BuildSubdirectoryList( szDirectory, true ); BuildBonusMapsList( szDirectory, true ); } else if ( Q_strcmp( pchType, "dir" ) == 0 ) { AddBonus( "", pKey->GetString(), true ); } else if ( Q_strcmp( pchType, "map" ) == 0 ) { AddBonus( "", pKey->GetString(), false ); } } } else { // Search through the current directory Q_snprintf( szDirectory, sizeof( szDirectory ), "%s/", pCurrentPath ); BuildSubdirectoryList( szDirectory, false ); BuildBonusMapsList( szDirectory, false ); } } void CBonusMapsDatabase::RefreshMapData( void ) { // Reset completion percentage m_iCompletableLevels = 0; m_fCurrentCompletion = 0.0f; for ( int iMap = 0; iMap < m_BonusMaps.Count(); ++iMap ) { BonusMapDescription_t *pMap = &m_BonusMaps[ iMap ]; float fCompletion = GetChallengeBests( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap ); // If all the challenges are completed set it as complete if ( fCompletion == 1.0f ) SetBooleanStatus( "complete", pMap->szFileName, pMap->szMapName, true ); GetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap ); if ( pMap->bComplete ) fCompletion = 1.0f; if ( !pMap->bIsFolder ) { m_fCurrentCompletion += fCompletion; ++m_iCompletableLevels; } } } int CBonusMapsDatabase::BonusCount( void ) { if ( m_BonusMaps.Count() == 0 ) ScanBonusMaps(); return m_BonusMaps.Count(); } bool CBonusMapsDatabase::GetBlink( void ) { KeyValues *pBlinkKey = m_pBonusMapSavedData->FindKey( "blink" ); if ( !pBlinkKey ) return false; return ( pBlinkKey->GetInt() != 0 ); } void CBonusMapsDatabase::SetBlink( bool bState ) { KeyValues *pBlinkKey = m_pBonusMapSavedData->FindKey( "blink" ); if ( pBlinkKey ) { bool bCurrentState = ( pBlinkKey->GetInt() != 0 ); if ( bState && !bCurrentState ) { pBlinkKey->SetStringValue( "1" ); m_bSavedDataChanged = true; } else if ( !bState && bCurrentState ) { pBlinkKey->SetStringValue( "0" ); m_bSavedDataChanged = true; } } } // Only used on X360 bool CBonusMapsDatabase::BonusesUnlocked( void ) { if ( m_iX360BonusesUnlocked == -1 ) { // Never checked, so set up the proper X360 scan BonusMapsDatabase()->ClearBonusMapsList(); // clear the current list BonusMapsDatabase()->RootPath(); BonusMapsDatabase()->ScanBonusMaps(); m_iX360BonusesUnlocked = 0; } if ( m_iX360BonusesUnlocked == 0 ) { // Hasn't been recorded as unlocked yet for ( int iBonusMap = 0; iBonusMap < BonusMapsDatabase()->BonusCount(); ++iBonusMap ) { BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData( iBonusMap ); if ( Q_strcmp( pMap->szMapName, "#Bonus_Map_AdvancedChambers" ) == 0 && !pMap->bLocked ) { // All bonuses unlocked, remember this and set up the proper X360 scan to get info. m_iX360BonusesUnlocked = 1; break; } } } return ( m_iX360BonusesUnlocked != 0 ); } void CBonusMapsDatabase::SetCurrentChallengeNames( const char *pchFileName, const char *pchMapName, const char *pchChallengeName ) { V_strcpy_safe( m_CurrentChallengeNames.szFileName, pchFileName ); V_strcpy_safe( m_CurrentChallengeNames.szMapName, pchMapName ); V_strcpy_safe( m_CurrentChallengeNames.szChallengeName, pchChallengeName ); } void CBonusMapsDatabase::GetCurrentChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName ) { Q_strcpy( pchFileName, m_CurrentChallengeNames.szFileName ); Q_strcpy( pchMapName, m_CurrentChallengeNames.szMapName ); Q_strcpy( pchChallengeName, m_CurrentChallengeNames.szChallengeName ); } void CBonusMapsDatabase::SetCurrentChallengeObjectives( int iBronze, int iSilver, int iGold ) { m_CurrentChallengeObjectives.iBronze = iBronze; m_CurrentChallengeObjectives.iSilver = iSilver; m_CurrentChallengeObjectives.iGold = iGold; } void CBonusMapsDatabase::GetCurrentChallengeObjectives( int &iBronze, int &iSilver, int &iGold ) { iBronze = m_CurrentChallengeObjectives.iBronze; iSilver = m_CurrentChallengeObjectives.iSilver; iGold = m_CurrentChallengeObjectives.iGold; } bool CBonusMapsDatabase::SetBooleanStatus( const char *pchName, const char *pchFileName, const char *pchMapName, bool bValue ) { bool bChanged = ::SetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), pchName, pchFileName, pchMapName, bValue ); if ( bChanged ) m_bSavedDataChanged = true; return bChanged; } bool CBonusMapsDatabase::SetBooleanStatus( const char *pchName, int iIndex, bool bValue ) { BonusMapDescription_t *pMap = &(m_BonusMaps[iIndex]); bool bChanged = SetBooleanStatus( pchName, pMap->szFileName, pMap->szMapName, bValue ); GetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap ); return bChanged; } bool CBonusMapsDatabase::UpdateChallengeBest( const char *pchFileName, const char *pchMapName, const char *pchChallengeName, int iBest ) { BonusMapChallenge_t challenge; V_strcpy_safe( challenge.szFileName, pchFileName ); V_strcpy_safe( challenge.szMapName, pchMapName ); V_strcpy_safe( challenge.szChallengeName, pchChallengeName ); challenge.iBest = iBest; bool bChanged = ::UpdateChallengeBest( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), challenge ); if ( bChanged ) m_bSavedDataChanged = true; return bChanged; } float CBonusMapsDatabase::GetCompletionPercentage( void ) { // Avoid divide by zero if ( m_iCompletableLevels <= 0 ) return 0.0f; return m_fCurrentCompletion / m_iCompletableLevels; } int CBonusMapsDatabase::NumAdvancedComplete( void ) { char szCurrentPath[_MAX_PATH]; V_strcpy_safe( szCurrentPath, m_szCurrentPath ); int iDirDepth = m_iDirDepth; BonusMapsDatabase()->ClearBonusMapsList(); SetPath( "./scripts/advanced_chambers", 1 ); ScanBonusMaps(); int iNumComplete = 0; // Look through all the bonus maps for ( int iBonusMap = 0; iBonusMap < BonusMapsDatabase()->BonusCount(); ++iBonusMap ) { BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData( iBonusMap ); if ( pMap && Q_strstr( pMap->szMapName, "Advanced" ) != NULL ) { // It's an advanced map, so check if it's complete if ( pMap->bComplete ) ++iNumComplete; } } BonusMapsDatabase()->ClearBonusMapsList(); SetPath( szCurrentPath, iDirDepth ); ScanBonusMaps(); return iNumComplete; } void CBonusMapsDatabase::NumMedals( int piNumMedals[ 3 ] ) { char szCurrentPath[_MAX_PATH]; V_strcpy_safe( szCurrentPath, m_szCurrentPath ); int iDirDepth = m_iDirDepth; BonusMapsDatabase()->ClearBonusMapsList(); SetPath( "./scripts/challenges", 1 ); ScanBonusMaps(); for ( int i = 0; i < 3; ++i ) piNumMedals[ i ] = 0; // Look through all the bonus maps for ( int iBonusMap = 0; iBonusMap < BonusMapsDatabase()->BonusCount(); ++iBonusMap ) { BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData( iBonusMap ); if ( pMap && pMap->m_pChallenges ) { for ( int iChallenge = 0; iChallenge < pMap->m_pChallenges->Count(); ++iChallenge ) { ChallengeDescription_t *pChallengeDescription = &((*pMap->m_pChallenges)[ iChallenge ]); int iBest, iEarnedMedal, iNext, iNextMedal; GetChallengeMedals( pChallengeDescription, iBest, iEarnedMedal, iNext, iNextMedal ); // Increase the count for this medal and every medal below it while ( iEarnedMedal > 0 ) { --iEarnedMedal; // Medals are 1,2&3 so subtract 1 first piNumMedals[ iEarnedMedal ]++; } } } } BonusMapsDatabase()->ClearBonusMapsList(); SetPath( szCurrentPath, iDirDepth ); ScanBonusMaps(); } void CBonusMapsDatabase::AddBonus( const char *pCurrentPath, const char *pDirFileName, bool bIsFolder ) { char szFileName[_MAX_PATH]; Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", pCurrentPath, pDirFileName ); // Only load bonus maps from the current mod's maps dir if( !IsX360() && !( g_pFullFileSystem->IsDirectory( szFileName, "MOD" ) || g_pFullFileSystem->FileExists( szFileName, "MOD" ) )) return; ParseBonusMapData( szFileName, pDirFileName, bIsFolder ); } void CBonusMapsDatabase::BuildSubdirectoryList( const char *pCurrentPath, bool bOutOfRoot ) { char szDirectory[_MAX_PATH]; Q_snprintf( szDirectory, sizeof( szDirectory ), "%s*", pCurrentPath ); FileFindHandle_t dirHandle; const char *pDirFileName = g_pFullFileSystem->FindFirst( szDirectory, &dirHandle ); while (pDirFileName) { // Skip it if it's not a directory, is the root, is back, or is an invalid folder if ( !g_pFullFileSystem->FindIsDirectory( dirHandle ) || Q_strcmp( pDirFileName, "." ) == 0 || Q_strcmp( pDirFileName, ".." ) == 0 || Q_stricmp( pDirFileName, "soundcache" ) == 0 || Q_stricmp( pDirFileName, "graphs" ) == 0 ) { pDirFileName = g_pFullFileSystem->FindNext( dirHandle ); continue; } if ( !bOutOfRoot ) AddBonus( pCurrentPath, pDirFileName, true ); else { char szFileName[_MAX_PATH]; Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", pCurrentPath, pDirFileName ); AddBonus( "", szFileName, true ); } pDirFileName = g_pFullFileSystem->FindNext( dirHandle ); } g_pFullFileSystem->FindClose( dirHandle ); } void CBonusMapsDatabase::BuildBonusMapsList( const char *pCurrentPath, bool bOutOfRoot ) { char szDirectory[_MAX_PATH]; Q_snprintf( szDirectory, sizeof( szDirectory ), "%s*.bns", pCurrentPath ); FileFindHandle_t mapHandle; const char *pMapFileName = g_pFullFileSystem->FindFirst( szDirectory, &mapHandle ); while ( pMapFileName && Q_strlen(pMapFileName)>0 ) { // Skip it if it's a directory or is the folder info if ( g_pFullFileSystem->FindIsDirectory( mapHandle ) || Q_strstr( pMapFileName, "folderinfo.bns" ) ) { pMapFileName = g_pFullFileSystem->FindNext( mapHandle ); continue; } if ( !bOutOfRoot ) AddBonus( pCurrentPath, pMapFileName, false ); else { char szFileName[_MAX_PATH]; Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", pCurrentPath, pMapFileName ); AddBonus( "", szFileName, false ); } pMapFileName = g_pFullFileSystem->FindNext( mapHandle ); } g_pFullFileSystem->FindClose( mapHandle ); } //----------------------------------------------------------------------------- // Purpose: Parses the save game info out of the .sav file header //----------------------------------------------------------------------------- void CBonusMapsDatabase::ParseBonusMapData( char const *pszFileName, char const *pszShortName, bool bIsFolder ) { if ( !pszFileName || !pszShortName ) return; char szMapInfo[_MAX_PATH]; // if it's a directory, there's no optional info if ( bIsFolder ) { // get the folder info file name Q_snprintf( szMapInfo, sizeof(szMapInfo), "%s/folderinfo.bns", pszFileName ); } else { // get the map info file name Q_strncpy( szMapInfo, pszFileName, sizeof(szMapInfo) ); } KeyValues *kv = new KeyValues( pszShortName ); if ( !kv->LoadFromFile( g_pFullFileSystem, szMapInfo, NULL ) ) DevMsg( "Unable to load bonus map info file %s\n", szMapInfo ); while ( kv ) { int iMap = m_BonusMaps.AddToTail(); BonusMapDescription_t *pMap = &m_BonusMaps[ iMap ]; // set required map data Q_strncpy( pMap->szFileName, pszFileName, sizeof(pMap->szFileName) ); Q_strncpy( pMap->szShortName, pszShortName, sizeof(pMap->szShortName) ); pMap->bIsFolder = bIsFolder; // set optional map data V_strcpy_safe( pMap->szMapName, kv->GetName() ); V_strcpy_safe( pMap->szMapFileName, kv->GetString( "map" ) ); V_strcpy_safe( pMap->szChapterName, kv->GetString( "chapter" ) ); V_strcpy_safe( pMap->szImageName, kv->GetString( "image" ) ); V_strcpy_safe( pMap->szComment, kv->GetString( "comment" ) ); pMap->bLocked = ( kv->GetInt( "lock", 0 ) != 0 ); pMap->bComplete = ( kv->GetInt( "complete", 0 ) != 0 ); float fCompletion = 0.0f; KeyValues *pChallenges = kv->FindKey( "challenges" ); if ( pChallenges ) { for ( KeyValues *pChallengeKey = pChallenges->GetFirstSubKey(); pChallengeKey; pChallengeKey = pChallengeKey->GetNextKey() ) { if ( !pMap->m_pChallenges ) pMap->m_pChallenges = new CUtlVector; int iChallenge = pMap->m_pChallenges->AddToTail(); ChallengeDescription_t *pChallenge = &(*pMap->m_pChallenges)[ iChallenge ]; V_strcpy_safe( pChallenge->szName, pChallengeKey->GetName() ); V_strcpy_safe( pChallenge->szComment, pChallengeKey->GetString( "comment" ) ); pChallenge->iType = pChallengeKey->GetInt( "type", -1 ); pChallenge->iBronze = pChallengeKey->GetInt( "bronze" ); pChallenge->iSilver = pChallengeKey->GetInt( "silver" ); pChallenge->iGold = pChallengeKey->GetInt( "gold" ); } fCompletion = GetChallengeBests( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap ); // If all the challenges are completed set it as complete if ( fCompletion == 1.0f ) SetBooleanStatus( "complete", pMap->szFileName, pMap->szMapName, true ); } // Get boolean status last because it can be altered if all the challenges were completed GetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap ); if ( pMap->bComplete ) fCompletion = 1.0f; if ( !pMap->bIsFolder ) { m_fCurrentCompletion += fCompletion; ++m_iCompletableLevels; kv = kv->GetNextTrueSubKey(); } else kv = NULL; } } void CC_BonusMapUnlock( const CCommand &args ) { if ( args.ArgC() < 3 ) { GameUI().BonusMapUnlock(); return; } GameUI().BonusMapUnlock( args[ 1 ], args[ 2 ] ); } static ConCommand sv_bonus_map_unlock("sv_bonus_map_unlock", CC_BonusMapUnlock, "Locks a bonus map.", FCVAR_CHEAT ); void CC_BonusMapComplete( const CCommand &args ) { if ( args.ArgC() < 3 ) { GameUI().BonusMapComplete(); return; } GameUI().BonusMapComplete( args[ 1 ], args[ 2 ] ); } static ConCommand sv_bonus_map_complete("sv_bonus_map_complete", CC_BonusMapComplete, "Completes a bonus map.", FCVAR_CHEAT ); void CC_BonusMapChallengeUpdate( const CCommand &args ) { if ( args.ArgC() < 5 ) { return; } GameUI().BonusMapChallengeUpdate( args[ 1 ], args[ 2 ], args[ 3 ], atoi( args[ 4 ] ) ); } static ConCommand sv_bonus_map_challenge_update("sv_bonus_map_challenge_update", CC_BonusMapChallengeUpdate, "Updates a bonus map challenge score.", FCVAR_CHEAT );