Team Fortress 2 Source Code as on 22/4/2020
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.

916 lines
25 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "BonusMapsDatabase.h"
  8. #include "EngineInterface.h"
  9. #include "tier1/convar.h"
  10. #include "tier1/utlbuffer.h"
  11. #include "filesystem.h"
  12. #include "ModInfo.h"
  13. #include "EngineInterface.h"
  14. #include "ixboxsystem.h"
  15. #include "KeyValues.h"
  16. #include "BasePanel.h"
  17. #include "GameUI_Interface.h"
  18. #include "BonusMapsDialog.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  21. #define MOD_DIR ( IsXbox() ? "DEFAULT_WRITE_PATH" : "MOD" )
  22. const char g_pszMedalNames[4][8] =
  23. {
  24. "none",
  25. "bronze",
  26. "silver",
  27. "gold"
  28. };
  29. const char *COM_GetModDirectory();
  30. bool WriteBonusMapSavedData( KeyValues *data )
  31. {
  32. if ( IsX360() && ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED ) )
  33. return false;
  34. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  35. data->RecursiveSaveToFile( buf, 0 );
  36. char szFilename[_MAX_PATH];
  37. if ( IsX360() )
  38. Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/bonus_maps_data.bmd" );
  39. else
  40. Q_snprintf( szFilename, sizeof( szFilename ), "save/bonus_maps_data.bmd" );
  41. bool bWriteSuccess = g_pFullFileSystem->WriteFile( szFilename, MOD_DIR, buf );
  42. xboxsystem->FinishContainerWrites();
  43. return bWriteSuccess;
  44. }
  45. void GetBooleanStatus( KeyValues *pBonusFilesKey, BonusMapDescription_t &map )
  46. {
  47. KeyValues *pFileKey = NULL;
  48. KeyValues *pBonusKey = NULL;
  49. for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() )
  50. {
  51. if ( Q_strcmp( pFileKey->GetName(), map.szFileName ) == 0 )
  52. {
  53. for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() )
  54. {
  55. if ( Q_strcmp( pBonusKey->GetName(), map.szMapName ) == 0 )
  56. {
  57. // Found the data
  58. break;
  59. }
  60. }
  61. break;
  62. }
  63. }
  64. if ( pBonusKey )
  65. {
  66. map.bLocked = ( pBonusKey->GetInt( "lock" ) != 0 );
  67. map.bComplete = ( pBonusKey->GetInt( "complete" ) != 0 );
  68. }
  69. }
  70. bool SetBooleanStatus( KeyValues *pBonusFilesKey, const char *pchName, const char *pchFileName, const char *pchMapName, bool bValue )
  71. {
  72. // Don't create entries for files that don't exist
  73. if ( !IsX360() && ! (g_pFullFileSystem->FileExists( pchFileName, "MOD" ) || g_pFullFileSystem->IsDirectory( pchFileName, "MOD" ) ) )
  74. {
  75. DevMsg( "Failed to set boolean status for file %s.", pchFileName );
  76. return false;
  77. }
  78. bool bChanged = false;
  79. KeyValues *pFileKey = NULL;
  80. KeyValues *pBonusKey = NULL;
  81. for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() )
  82. {
  83. if ( Q_strcmp( pFileKey->GetName(), pchFileName ) == 0 )
  84. {
  85. for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() )
  86. {
  87. if ( Q_strcmp( pBonusKey->GetName(), pchMapName ) == 0 )
  88. {
  89. // Found the data
  90. break;
  91. }
  92. }
  93. break;
  94. }
  95. }
  96. if ( !pFileKey )
  97. {
  98. // Didn't find it, so create a new spot for the data
  99. pFileKey = new KeyValues( pchFileName );
  100. pBonusFilesKey->AddSubKey( pFileKey );
  101. }
  102. if ( !pBonusKey )
  103. {
  104. pBonusKey = new KeyValues( pchMapName, pchName, "0" );
  105. pFileKey->AddSubKey( pBonusKey );
  106. bChanged = true;
  107. }
  108. if ( ( pBonusKey->GetInt( pchName ) != 0 ) != bValue )
  109. {
  110. bChanged = true;
  111. pBonusKey->SetInt( pchName, bValue );
  112. }
  113. return bChanged;
  114. }
  115. float GetChallengeBests( KeyValues *pBonusFilesKey, BonusMapDescription_t &challenge )
  116. {
  117. // There's no challenges, so bail and assume 0% challenge completion
  118. if ( challenge.m_pChallenges == NULL || challenge.m_pChallenges->Count() == 0 )
  119. return 0.0f;
  120. KeyValues *pFileKey = NULL;
  121. KeyValues *pBonusKey = NULL;
  122. for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() )
  123. {
  124. if ( Q_strcmp( pFileKey->GetName(), challenge.szFileName ) == 0 )
  125. {
  126. for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() )
  127. {
  128. if ( Q_strcmp( pBonusKey->GetName(), challenge.szMapName ) == 0 )
  129. {
  130. // Found the data
  131. break;
  132. }
  133. }
  134. break;
  135. }
  136. }
  137. float fChallengePoints = 0.0f;
  138. for ( int iChallenge = 0; iChallenge < challenge.m_pChallenges->Count(); ++iChallenge )
  139. {
  140. ChallengeDescription_t *pChallengeDescription = &((*challenge.m_pChallenges)[ iChallenge ]);
  141. pChallengeDescription->iBest = ( ( pBonusKey ) ? ( pBonusKey->GetInt( pChallengeDescription->szName, -1 ) ) : ( -1 ) );
  142. if ( pChallengeDescription->iBest >= 0 )
  143. {
  144. if ( pChallengeDescription->iBest <= pChallengeDescription->iGold )
  145. fChallengePoints += 3.0f;
  146. else if ( pChallengeDescription->iBest <= pChallengeDescription->iSilver )
  147. fChallengePoints += 2.0f;
  148. else if ( pChallengeDescription->iBest <= pChallengeDescription->iBronze )
  149. fChallengePoints += 1.0f;
  150. }
  151. }
  152. return fChallengePoints / ( challenge.m_pChallenges->Count() * 3.0f );
  153. }
  154. bool UpdateChallengeBest( KeyValues *pBonusFilesKey, const BonusMapChallenge_t &challenge )
  155. {
  156. // Don't create entries for files that don't exist
  157. if ( !IsX360() && !g_pFullFileSystem->FileExists( challenge.szFileName, "MOD" ) )
  158. {
  159. DevMsg( "Failed to set challenge best for file %s.", challenge.szFileName );
  160. return false;
  161. }
  162. bool bChanged = false;
  163. KeyValues *pFileKey = NULL;
  164. KeyValues *pBonusKey = NULL;
  165. for ( pFileKey = pBonusFilesKey->GetFirstSubKey(); pFileKey; pFileKey = pFileKey->GetNextTrueSubKey() )
  166. {
  167. if ( Q_strcmp( pFileKey->GetName(), challenge.szFileName ) == 0 )
  168. {
  169. for ( pBonusKey = pFileKey->GetFirstSubKey(); pBonusKey; pBonusKey = pBonusKey->GetNextKey() )
  170. {
  171. if ( Q_strcmp( pBonusKey->GetName(), challenge.szMapName ) == 0 )
  172. {
  173. // Found the challenge
  174. break;
  175. }
  176. }
  177. break;
  178. }
  179. }
  180. if ( !pFileKey )
  181. {
  182. // Didn't find it, so create a new spot for data
  183. pFileKey = new KeyValues( challenge.szFileName );
  184. pBonusFilesKey->AddSubKey( pFileKey );
  185. }
  186. if ( !pBonusKey )
  187. {
  188. pBonusKey = new KeyValues( challenge.szMapName, challenge.szChallengeName, -1 );
  189. pFileKey->AddSubKey( pBonusKey );
  190. bChanged = true;
  191. }
  192. int iCurrentBest = pBonusKey->GetInt( challenge.szChallengeName, -1 );
  193. if ( iCurrentBest == -1 || iCurrentBest > challenge.iBest )
  194. {
  195. bChanged = true;
  196. pBonusKey->SetInt( challenge.szChallengeName, challenge.iBest );
  197. }
  198. return bChanged;
  199. }
  200. void GetChallengeMedals( ChallengeDescription_t *pChallengeDescription, int &iBest, int &iEarnedMedal, int &iNext, int &iNextMedal )
  201. {
  202. iBest = pChallengeDescription->iBest;
  203. if ( iBest == -1 )
  204. iEarnedMedal = 0;
  205. else if ( iBest <= pChallengeDescription->iGold )
  206. iEarnedMedal = 3;
  207. else if ( iBest <= pChallengeDescription->iSilver )
  208. iEarnedMedal = 2;
  209. else if ( iBest <= pChallengeDescription->iBronze )
  210. iEarnedMedal = 1;
  211. else
  212. iEarnedMedal = 0;
  213. iNext = -1;
  214. switch ( iEarnedMedal )
  215. {
  216. case 0:
  217. iNext = pChallengeDescription->iBronze;
  218. iNextMedal = 1;
  219. break;
  220. case 1:
  221. iNext = pChallengeDescription->iSilver;
  222. iNextMedal = 2;
  223. break;
  224. case 2:
  225. iNext = pChallengeDescription->iGold;
  226. iNextMedal = 3;
  227. break;
  228. case 3:
  229. iNext = -1;
  230. iNextMedal = -1;
  231. break;
  232. }
  233. }
  234. CBonusMapsDatabase *g_pBonusMapsDatabase = NULL;
  235. CBonusMapsDatabase *BonusMapsDatabase( void )
  236. {
  237. if ( !g_pBonusMapsDatabase )
  238. static CBonusMapsDatabase StaticBonusMapsDatabase;
  239. return g_pBonusMapsDatabase;
  240. }
  241. //-----------------------------------------------------------------------------
  242. // Purpose:Constructor
  243. //-----------------------------------------------------------------------------
  244. CBonusMapsDatabase::CBonusMapsDatabase( void )
  245. {
  246. Assert( g_pBonusMapsDatabase == NULL ); // There should only be 1 bonus maps database
  247. g_pBonusMapsDatabase = this;
  248. RootPath();
  249. m_pBonusMapsManifest = new KeyValues( "bonus_maps_manifest" );
  250. m_pBonusMapsManifest->LoadFromFile( g_pFullFileSystem, "scripts/bonus_maps_manifest.txt", NULL );
  251. m_iX360BonusesUnlocked = -1; // Only used on X360
  252. m_bHasLoadedSaveData = false;
  253. ReadBonusMapSaveData();
  254. }
  255. //-----------------------------------------------------------------------------
  256. // Purpose: Destructor
  257. //-----------------------------------------------------------------------------
  258. CBonusMapsDatabase::~CBonusMapsDatabase()
  259. {
  260. g_pBonusMapsDatabase = NULL;
  261. }
  262. extern bool g_bIsCreatingNewGameMenuForPreFetching;
  263. bool CBonusMapsDatabase::ReadBonusMapSaveData( void )
  264. {
  265. if ( !m_pBonusMapSavedData )
  266. m_pBonusMapSavedData = new KeyValues( "bonus_map_saved_data" );
  267. if ( g_bIsCreatingNewGameMenuForPreFetching )
  268. {
  269. // Although we may have a storage device it's not going to be able to find our file at this point! BAIL!
  270. return false;
  271. }
  272. #ifdef _X360
  273. // Nothing to read
  274. if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
  275. return false;
  276. #endif
  277. char szFilename[_MAX_PATH];
  278. if ( IsX360() )
  279. Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/bonus_maps_data.bmd" );
  280. else
  281. Q_snprintf( szFilename, sizeof( szFilename ), "save/bonus_maps_data.bmd" );
  282. m_pBonusMapSavedData->LoadFromFile( g_pFullFileSystem, szFilename, NULL );
  283. m_bSavedDataChanged = false;
  284. m_bHasLoadedSaveData = true;
  285. return true;
  286. }
  287. bool CBonusMapsDatabase::WriteSaveData( void )
  288. {
  289. bool bSuccess = false;
  290. if ( m_bSavedDataChanged )
  291. bSuccess = WriteBonusMapSavedData( m_pBonusMapSavedData );
  292. if ( bSuccess )
  293. m_bSavedDataChanged = false;
  294. return bSuccess;
  295. }
  296. void CBonusMapsDatabase::RootPath( void )
  297. {
  298. m_iDirDepth = 0;
  299. V_strcpy_safe( m_szCurrentPath, "." );
  300. }
  301. void CBonusMapsDatabase::AppendPath( const char *pchAppend )
  302. {
  303. ++m_iDirDepth;
  304. char szCurPathTmp[MAX_PATH];
  305. V_strcpy_safe( szCurPathTmp, m_szCurrentPath );
  306. Q_snprintf( m_szCurrentPath, sizeof( m_szCurrentPath ), "%s/%s", szCurPathTmp, pchAppend );
  307. }
  308. void CBonusMapsDatabase::BackPath( void )
  309. {
  310. if ( m_iDirDepth == 0 )
  311. return;
  312. if ( m_iDirDepth == 1 )
  313. {
  314. RootPath(); // back to root
  315. return;
  316. }
  317. --m_iDirDepth;
  318. Q_strrchr( m_szCurrentPath, '/' )[ 0 ] = '\0'; // remove a dir from the end
  319. }
  320. void CBonusMapsDatabase::SetPath( const char *pchPath, int iDirDepth )
  321. {
  322. V_strcpy_safe( m_szCurrentPath, pchPath );
  323. m_iDirDepth = iDirDepth;
  324. }
  325. void CBonusMapsDatabase::ClearBonusMapsList( void )
  326. {
  327. m_BonusMaps.RemoveAll();
  328. }
  329. //-----------------------------------------------------------------------------
  330. // Purpose: builds bonus map list from directory
  331. //-----------------------------------------------------------------------------
  332. void CBonusMapsDatabase::ScanBonusMaps( void )
  333. {
  334. // Don't load in the bonus maps before we've properly read in the save data
  335. if ( !m_bHasLoadedSaveData )
  336. {
  337. if ( ! ReadBonusMapSaveData() )
  338. return;
  339. }
  340. char *pCurrentPath = &(m_szCurrentPath[2]);
  341. // Reset completion percentage
  342. m_iCompletableLevels = 0;
  343. m_fCurrentCompletion = 0.0f;
  344. // populate list box with all bonus maps in the current path
  345. char szDirectory[_MAX_PATH];
  346. if ( Q_strcmp( m_szCurrentPath, "." ) == 0 )
  347. {
  348. // We're at the root, so look at the directories in the manifest
  349. KeyValues *pKey = NULL;
  350. for ( pKey = m_pBonusMapsManifest->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
  351. {
  352. const char *pchType = pKey->GetName();
  353. if ( Q_strcmp( pchType, "search" ) == 0 )
  354. {
  355. // Search through the directory
  356. Q_snprintf( szDirectory, sizeof( szDirectory ), "%s/", pKey->GetString() );
  357. BuildSubdirectoryList( szDirectory, true );
  358. BuildBonusMapsList( szDirectory, true );
  359. }
  360. else if ( Q_strcmp( pchType, "dir" ) == 0 )
  361. {
  362. AddBonus( "", pKey->GetString(), true );
  363. }
  364. else if ( Q_strcmp( pchType, "map" ) == 0 )
  365. {
  366. AddBonus( "", pKey->GetString(), false );
  367. }
  368. }
  369. }
  370. else
  371. {
  372. // Search through the current directory
  373. Q_snprintf( szDirectory, sizeof( szDirectory ), "%s/", pCurrentPath );
  374. BuildSubdirectoryList( szDirectory, false );
  375. BuildBonusMapsList( szDirectory, false );
  376. }
  377. }
  378. void CBonusMapsDatabase::RefreshMapData( void )
  379. {
  380. // Reset completion percentage
  381. m_iCompletableLevels = 0;
  382. m_fCurrentCompletion = 0.0f;
  383. for ( int iMap = 0; iMap < m_BonusMaps.Count(); ++iMap )
  384. {
  385. BonusMapDescription_t *pMap = &m_BonusMaps[ iMap ];
  386. float fCompletion = GetChallengeBests( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap );
  387. // If all the challenges are completed set it as complete
  388. if ( fCompletion == 1.0f )
  389. SetBooleanStatus( "complete", pMap->szFileName, pMap->szMapName, true );
  390. GetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap );
  391. if ( pMap->bComplete )
  392. fCompletion = 1.0f;
  393. if ( !pMap->bIsFolder )
  394. {
  395. m_fCurrentCompletion += fCompletion;
  396. ++m_iCompletableLevels;
  397. }
  398. }
  399. }
  400. int CBonusMapsDatabase::BonusCount( void )
  401. {
  402. if ( m_BonusMaps.Count() == 0 )
  403. ScanBonusMaps();
  404. return m_BonusMaps.Count();
  405. }
  406. bool CBonusMapsDatabase::GetBlink( void )
  407. {
  408. KeyValues *pBlinkKey = m_pBonusMapSavedData->FindKey( "blink" );
  409. if ( !pBlinkKey )
  410. return false;
  411. return ( pBlinkKey->GetInt() != 0 );
  412. }
  413. void CBonusMapsDatabase::SetBlink( bool bState )
  414. {
  415. KeyValues *pBlinkKey = m_pBonusMapSavedData->FindKey( "blink" );
  416. if ( pBlinkKey )
  417. {
  418. bool bCurrentState = ( pBlinkKey->GetInt() != 0 );
  419. if ( bState && !bCurrentState )
  420. {
  421. pBlinkKey->SetStringValue( "1" );
  422. m_bSavedDataChanged = true;
  423. }
  424. else if ( !bState && bCurrentState )
  425. {
  426. pBlinkKey->SetStringValue( "0" );
  427. m_bSavedDataChanged = true;
  428. }
  429. }
  430. }
  431. // Only used on X360
  432. bool CBonusMapsDatabase::BonusesUnlocked( void )
  433. {
  434. if ( m_iX360BonusesUnlocked == -1 )
  435. {
  436. // Never checked, so set up the proper X360 scan
  437. BonusMapsDatabase()->ClearBonusMapsList(); // clear the current list
  438. BonusMapsDatabase()->RootPath();
  439. BonusMapsDatabase()->ScanBonusMaps();
  440. m_iX360BonusesUnlocked = 0;
  441. }
  442. if ( m_iX360BonusesUnlocked == 0 )
  443. {
  444. // Hasn't been recorded as unlocked yet
  445. for ( int iBonusMap = 0; iBonusMap < BonusMapsDatabase()->BonusCount(); ++iBonusMap )
  446. {
  447. BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData( iBonusMap );
  448. if ( Q_strcmp( pMap->szMapName, "#Bonus_Map_AdvancedChambers" ) == 0 && !pMap->bLocked )
  449. {
  450. // All bonuses unlocked, remember this and set up the proper X360 scan to get info.
  451. m_iX360BonusesUnlocked = 1;
  452. break;
  453. }
  454. }
  455. }
  456. return ( m_iX360BonusesUnlocked != 0 );
  457. }
  458. void CBonusMapsDatabase::SetCurrentChallengeNames( const char *pchFileName, const char *pchMapName, const char *pchChallengeName )
  459. {
  460. V_strcpy_safe( m_CurrentChallengeNames.szFileName, pchFileName );
  461. V_strcpy_safe( m_CurrentChallengeNames.szMapName, pchMapName );
  462. V_strcpy_safe( m_CurrentChallengeNames.szChallengeName, pchChallengeName );
  463. }
  464. void CBonusMapsDatabase::GetCurrentChallengeNames( char *pchFileName, char *pchMapName, char *pchChallengeName )
  465. {
  466. Q_strcpy( pchFileName, m_CurrentChallengeNames.szFileName );
  467. Q_strcpy( pchMapName, m_CurrentChallengeNames.szMapName );
  468. Q_strcpy( pchChallengeName, m_CurrentChallengeNames.szChallengeName );
  469. }
  470. void CBonusMapsDatabase::SetCurrentChallengeObjectives( int iBronze, int iSilver, int iGold )
  471. {
  472. m_CurrentChallengeObjectives.iBronze = iBronze;
  473. m_CurrentChallengeObjectives.iSilver = iSilver;
  474. m_CurrentChallengeObjectives.iGold = iGold;
  475. }
  476. void CBonusMapsDatabase::GetCurrentChallengeObjectives( int &iBronze, int &iSilver, int &iGold )
  477. {
  478. iBronze = m_CurrentChallengeObjectives.iBronze;
  479. iSilver = m_CurrentChallengeObjectives.iSilver;
  480. iGold = m_CurrentChallengeObjectives.iGold;
  481. }
  482. bool CBonusMapsDatabase::SetBooleanStatus( const char *pchName, const char *pchFileName, const char *pchMapName, bool bValue )
  483. {
  484. bool bChanged = ::SetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), pchName, pchFileName, pchMapName, bValue );
  485. if ( bChanged )
  486. m_bSavedDataChanged = true;
  487. return bChanged;
  488. }
  489. bool CBonusMapsDatabase::SetBooleanStatus( const char *pchName, int iIndex, bool bValue )
  490. {
  491. BonusMapDescription_t *pMap = &(m_BonusMaps[iIndex]);
  492. bool bChanged = SetBooleanStatus( pchName, pMap->szFileName, pMap->szMapName, bValue );
  493. GetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap );
  494. return bChanged;
  495. }
  496. bool CBonusMapsDatabase::UpdateChallengeBest( const char *pchFileName, const char *pchMapName, const char *pchChallengeName, int iBest )
  497. {
  498. BonusMapChallenge_t challenge;
  499. V_strcpy_safe( challenge.szFileName, pchFileName );
  500. V_strcpy_safe( challenge.szMapName, pchMapName );
  501. V_strcpy_safe( challenge.szChallengeName, pchChallengeName );
  502. challenge.iBest = iBest;
  503. bool bChanged = ::UpdateChallengeBest( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), challenge );
  504. if ( bChanged )
  505. m_bSavedDataChanged = true;
  506. return bChanged;
  507. }
  508. float CBonusMapsDatabase::GetCompletionPercentage( void )
  509. {
  510. // Avoid divide by zero
  511. if ( m_iCompletableLevels <= 0 )
  512. return 0.0f;
  513. return m_fCurrentCompletion / m_iCompletableLevels;
  514. }
  515. int CBonusMapsDatabase::NumAdvancedComplete( void )
  516. {
  517. char szCurrentPath[_MAX_PATH];
  518. V_strcpy_safe( szCurrentPath, m_szCurrentPath );
  519. int iDirDepth = m_iDirDepth;
  520. BonusMapsDatabase()->ClearBonusMapsList();
  521. SetPath( "./scripts/advanced_chambers", 1 );
  522. ScanBonusMaps();
  523. int iNumComplete = 0;
  524. // Look through all the bonus maps
  525. for ( int iBonusMap = 0; iBonusMap < BonusMapsDatabase()->BonusCount(); ++iBonusMap )
  526. {
  527. BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData( iBonusMap );
  528. if ( pMap && Q_strstr( pMap->szMapName, "Advanced" ) != NULL )
  529. {
  530. // It's an advanced map, so check if it's complete
  531. if ( pMap->bComplete )
  532. ++iNumComplete;
  533. }
  534. }
  535. BonusMapsDatabase()->ClearBonusMapsList();
  536. SetPath( szCurrentPath, iDirDepth );
  537. ScanBonusMaps();
  538. return iNumComplete;
  539. }
  540. void CBonusMapsDatabase::NumMedals( int piNumMedals[ 3 ] )
  541. {
  542. char szCurrentPath[_MAX_PATH];
  543. V_strcpy_safe( szCurrentPath, m_szCurrentPath );
  544. int iDirDepth = m_iDirDepth;
  545. BonusMapsDatabase()->ClearBonusMapsList();
  546. SetPath( "./scripts/challenges", 1 );
  547. ScanBonusMaps();
  548. for ( int i = 0; i < 3; ++i )
  549. piNumMedals[ i ] = 0;
  550. // Look through all the bonus maps
  551. for ( int iBonusMap = 0; iBonusMap < BonusMapsDatabase()->BonusCount(); ++iBonusMap )
  552. {
  553. BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData( iBonusMap );
  554. if ( pMap && pMap->m_pChallenges )
  555. {
  556. for ( int iChallenge = 0; iChallenge < pMap->m_pChallenges->Count(); ++iChallenge )
  557. {
  558. ChallengeDescription_t *pChallengeDescription = &((*pMap->m_pChallenges)[ iChallenge ]);
  559. int iBest, iEarnedMedal, iNext, iNextMedal;
  560. GetChallengeMedals( pChallengeDescription, iBest, iEarnedMedal, iNext, iNextMedal );
  561. // Increase the count for this medal and every medal below it
  562. while ( iEarnedMedal > 0 )
  563. {
  564. --iEarnedMedal; // Medals are 1,2&3 so subtract 1 first
  565. piNumMedals[ iEarnedMedal ]++;
  566. }
  567. }
  568. }
  569. }
  570. BonusMapsDatabase()->ClearBonusMapsList();
  571. SetPath( szCurrentPath, iDirDepth );
  572. ScanBonusMaps();
  573. }
  574. void CBonusMapsDatabase::AddBonus( const char *pCurrentPath, const char *pDirFileName, bool bIsFolder )
  575. {
  576. char szFileName[_MAX_PATH];
  577. Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", pCurrentPath, pDirFileName );
  578. // Only load bonus maps from the current mod's maps dir
  579. if( !IsX360() && !( g_pFullFileSystem->IsDirectory( szFileName, "MOD" ) || g_pFullFileSystem->FileExists( szFileName, "MOD" ) ))
  580. return;
  581. ParseBonusMapData( szFileName, pDirFileName, bIsFolder );
  582. }
  583. void CBonusMapsDatabase::BuildSubdirectoryList( const char *pCurrentPath, bool bOutOfRoot )
  584. {
  585. char szDirectory[_MAX_PATH];
  586. Q_snprintf( szDirectory, sizeof( szDirectory ), "%s*", pCurrentPath );
  587. FileFindHandle_t dirHandle;
  588. const char *pDirFileName = g_pFullFileSystem->FindFirst( szDirectory, &dirHandle );
  589. while (pDirFileName)
  590. {
  591. // Skip it if it's not a directory, is the root, is back, or is an invalid folder
  592. if ( !g_pFullFileSystem->FindIsDirectory( dirHandle ) ||
  593. Q_strcmp( pDirFileName, "." ) == 0 ||
  594. Q_strcmp( pDirFileName, ".." ) == 0 ||
  595. Q_stricmp( pDirFileName, "soundcache" ) == 0 ||
  596. Q_stricmp( pDirFileName, "graphs" ) == 0 )
  597. {
  598. pDirFileName = g_pFullFileSystem->FindNext( dirHandle );
  599. continue;
  600. }
  601. if ( !bOutOfRoot )
  602. AddBonus( pCurrentPath, pDirFileName, true );
  603. else
  604. {
  605. char szFileName[_MAX_PATH];
  606. Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", pCurrentPath, pDirFileName );
  607. AddBonus( "", szFileName, true );
  608. }
  609. pDirFileName = g_pFullFileSystem->FindNext( dirHandle );
  610. }
  611. g_pFullFileSystem->FindClose( dirHandle );
  612. }
  613. void CBonusMapsDatabase::BuildBonusMapsList( const char *pCurrentPath, bool bOutOfRoot )
  614. {
  615. char szDirectory[_MAX_PATH];
  616. Q_snprintf( szDirectory, sizeof( szDirectory ), "%s*.bns", pCurrentPath );
  617. FileFindHandle_t mapHandle;
  618. const char *pMapFileName = g_pFullFileSystem->FindFirst( szDirectory, &mapHandle );
  619. while ( pMapFileName && Q_strlen(pMapFileName)>0 )
  620. {
  621. // Skip it if it's a directory or is the folder info
  622. if ( g_pFullFileSystem->FindIsDirectory( mapHandle ) || Q_strstr( pMapFileName, "folderinfo.bns" ) )
  623. {
  624. pMapFileName = g_pFullFileSystem->FindNext( mapHandle );
  625. continue;
  626. }
  627. if ( !bOutOfRoot )
  628. AddBonus( pCurrentPath, pMapFileName, false );
  629. else
  630. {
  631. char szFileName[_MAX_PATH];
  632. Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", pCurrentPath, pMapFileName );
  633. AddBonus( "", szFileName, false );
  634. }
  635. pMapFileName = g_pFullFileSystem->FindNext( mapHandle );
  636. }
  637. g_pFullFileSystem->FindClose( mapHandle );
  638. }
  639. //-----------------------------------------------------------------------------
  640. // Purpose: Parses the save game info out of the .sav file header
  641. //-----------------------------------------------------------------------------
  642. void CBonusMapsDatabase::ParseBonusMapData( char const *pszFileName, char const *pszShortName, bool bIsFolder )
  643. {
  644. if ( !pszFileName || !pszShortName )
  645. return;
  646. char szMapInfo[_MAX_PATH];
  647. // if it's a directory, there's no optional info
  648. if ( bIsFolder )
  649. {
  650. // get the folder info file name
  651. Q_snprintf( szMapInfo, sizeof(szMapInfo), "%s/folderinfo.bns", pszFileName );
  652. }
  653. else
  654. {
  655. // get the map info file name
  656. Q_strncpy( szMapInfo, pszFileName, sizeof(szMapInfo) );
  657. }
  658. KeyValues *kv = new KeyValues( pszShortName );
  659. if ( !kv->LoadFromFile( g_pFullFileSystem, szMapInfo, NULL ) )
  660. DevMsg( "Unable to load bonus map info file %s\n", szMapInfo );
  661. while ( kv )
  662. {
  663. int iMap = m_BonusMaps.AddToTail();
  664. BonusMapDescription_t *pMap = &m_BonusMaps[ iMap ];
  665. // set required map data
  666. Q_strncpy( pMap->szFileName, pszFileName, sizeof(pMap->szFileName) );
  667. Q_strncpy( pMap->szShortName, pszShortName, sizeof(pMap->szShortName) );
  668. pMap->bIsFolder = bIsFolder;
  669. // set optional map data
  670. V_strcpy_safe( pMap->szMapName, kv->GetName() );
  671. V_strcpy_safe( pMap->szMapFileName, kv->GetString( "map" ) );
  672. V_strcpy_safe( pMap->szChapterName, kv->GetString( "chapter" ) );
  673. V_strcpy_safe( pMap->szImageName, kv->GetString( "image" ) );
  674. V_strcpy_safe( pMap->szComment, kv->GetString( "comment" ) );
  675. pMap->bLocked = ( kv->GetInt( "lock", 0 ) != 0 );
  676. pMap->bComplete = ( kv->GetInt( "complete", 0 ) != 0 );
  677. float fCompletion = 0.0f;
  678. KeyValues *pChallenges = kv->FindKey( "challenges" );
  679. if ( pChallenges )
  680. {
  681. for ( KeyValues *pChallengeKey = pChallenges->GetFirstSubKey(); pChallengeKey; pChallengeKey = pChallengeKey->GetNextKey() )
  682. {
  683. if ( !pMap->m_pChallenges )
  684. pMap->m_pChallenges = new CUtlVector<ChallengeDescription_t>;
  685. int iChallenge = pMap->m_pChallenges->AddToTail();
  686. ChallengeDescription_t *pChallenge = &(*pMap->m_pChallenges)[ iChallenge ];
  687. V_strcpy_safe( pChallenge->szName, pChallengeKey->GetName() );
  688. V_strcpy_safe( pChallenge->szComment, pChallengeKey->GetString( "comment" ) );
  689. pChallenge->iType = pChallengeKey->GetInt( "type", -1 );
  690. pChallenge->iBronze = pChallengeKey->GetInt( "bronze" );
  691. pChallenge->iSilver = pChallengeKey->GetInt( "silver" );
  692. pChallenge->iGold = pChallengeKey->GetInt( "gold" );
  693. }
  694. fCompletion = GetChallengeBests( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap );
  695. // If all the challenges are completed set it as complete
  696. if ( fCompletion == 1.0f )
  697. SetBooleanStatus( "complete", pMap->szFileName, pMap->szMapName, true );
  698. }
  699. // Get boolean status last because it can be altered if all the challenges were completed
  700. GetBooleanStatus( m_pBonusMapSavedData->FindKey( "bonusfiles", true ), *pMap );
  701. if ( pMap->bComplete )
  702. fCompletion = 1.0f;
  703. if ( !pMap->bIsFolder )
  704. {
  705. m_fCurrentCompletion += fCompletion;
  706. ++m_iCompletableLevels;
  707. kv = kv->GetNextTrueSubKey();
  708. }
  709. else
  710. kv = NULL;
  711. }
  712. }
  713. void CC_BonusMapUnlock( const CCommand &args )
  714. {
  715. if ( args.ArgC() < 3 )
  716. {
  717. GameUI().BonusMapUnlock();
  718. return;
  719. }
  720. GameUI().BonusMapUnlock( args[ 1 ], args[ 2 ] );
  721. }
  722. static ConCommand sv_bonus_map_unlock("sv_bonus_map_unlock", CC_BonusMapUnlock, "Locks a bonus map.", FCVAR_CHEAT );
  723. void CC_BonusMapComplete( const CCommand &args )
  724. {
  725. if ( args.ArgC() < 3 )
  726. {
  727. GameUI().BonusMapComplete();
  728. return;
  729. }
  730. GameUI().BonusMapComplete( args[ 1 ], args[ 2 ] );
  731. }
  732. static ConCommand sv_bonus_map_complete("sv_bonus_map_complete", CC_BonusMapComplete, "Completes a bonus map.", FCVAR_CHEAT );
  733. void CC_BonusMapChallengeUpdate( const CCommand &args )
  734. {
  735. if ( args.ArgC() < 5 )
  736. {
  737. return;
  738. }
  739. GameUI().BonusMapChallengeUpdate( args[ 1 ], args[ 2 ], args[ 3 ], atoi( args[ 4 ] ) );
  740. }
  741. static ConCommand sv_bonus_map_challenge_update("sv_bonus_map_challenge_update", CC_BonusMapChallengeUpdate, "Updates a bonus map challenge score.", FCVAR_CHEAT );