diff --git a/stepmania/src/GameState.cpp b/stepmania/src/GameState.cpp index 03a3463c60..c0e932cdc1 100644 --- a/stepmania/src/GameState.cpp +++ b/stepmania/src/GameState.cpp @@ -31,8 +31,6 @@ #include "ProfileManager.h" #include "arch/arch.h" #include "ThemeManager.h" -//#include "fstream" -#include "Bookkeeper.h" #include "LightsManager.h" #include "RageFile.h" @@ -144,10 +142,6 @@ void GameState::Reset() PROFILEMAN->UnloadProfile( (PlayerNumber)p ); } - // save stats info intermitently in case of crash - BOOKKEEPER->WriteToDisk(); - SONGMAN->SaveMachineScoresToDisk(); - LIGHTSMAN->SetLightMode( LIGHTMODE_ATTRACT ); } @@ -959,7 +953,7 @@ void GameState::GetRankingFeats( PlayerNumber pn, vector &asFeatsO for( i=0; i &vHighScores = SONGMAN->m_CategoryDatas[nt][i].vHighScores; + vector &vHighScores = PROFILEMAN->m_CategoryDatas[nt][i].vHighScores; for( unsigned j=0; jm_sMemoryCardDir[p] != "" ) FILEMAN->Mount( "dir", PREFSMAN->m_sMemoryCardDir[p], MemCardDirs[p] ); } + + InitMachineScoresFromDisk(); } ProfileManager::~ProfileManager() { + WriteStatsWebPage(); + SaveMachineScoresToDisk(); } void ProfileManager::GetMachineProfileIDs( vector &asProfileIDsOut ) @@ -96,8 +112,8 @@ bool ProfileManager::LoadProfile( PlayerNumber pn, CString sProfileDir, bool bIs } // Load scores into SONGMAN - SONGMAN->ReadStepsMemCardDataFromFile( m_sProfileDir[pn]+STEPS_MEM_CARD_DATA_FILE, (MemoryCard)pn ); - SONGMAN->ReadCourseMemCardDataFromFile( m_sProfileDir[pn]+COURSE_MEM_CARD_DATA_FILE, (MemoryCard)pn ); + PROFILEMAN->ReadStepsMemCardDataFromFile( m_sProfileDir[pn]+STEPS_MEM_CARD_DATA_FILE, (MemoryCard)pn ); + PROFILEMAN->ReadCourseMemCardDataFromFile( m_sProfileDir[pn]+COURSE_MEM_CARD_DATA_FILE, (MemoryCard)pn ); // apply saved default modifiers if any if( m_Profile[pn].m_bUsingProfileDefaultModifiers ) @@ -190,8 +206,8 @@ bool ProfileManager::SaveProfile( PlayerNumber pn ) // Save scores into SONGMAN // // TODO: move record data to this class - SONGMAN->SaveStepsMemCardDataToFile( m_sProfileDir[pn]+STEPS_MEM_CARD_DATA_FILE, (MemoryCard)pn ); - SONGMAN->SaveCourseMemCardDataToFile( m_sProfileDir[pn]+COURSE_MEM_CARD_DATA_FILE, (MemoryCard)pn ); + SaveStepsMemCardDataToFile( m_sProfileDir[pn]+STEPS_MEM_CARD_DATA_FILE, (MemoryCard)pn ); + SaveCourseMemCardDataToFile( m_sProfileDir[pn]+COURSE_MEM_CARD_DATA_FILE, (MemoryCard)pn ); return true; } @@ -320,3 +336,666 @@ bool ProfileManager::DeleteMachineProfile( CString sProfileID ) else return true; } + +void ProfileManager::SaveMachineScoresToDisk() +{ + SaveCategoryRankingsToFile( CATEGORY_RANKING_FILE ); + SaveStepsMemCardDataToFile( MACHINE_STEPS_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); + SaveCourseMemCardDataToFile( MACHINE_COURSE_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); +} + +void ProfileManager::CategoryData::AddHighScore( HighScore hs, int &iIndexOut ) +{ + int i; + for( i=0; i<(int)vHighScores.size(); i++ ) + { + if( hs >= vHighScores[i] ) + break; + } + if( i < NUM_RANKING_LINES ) + { + vHighScores.insert( vHighScores.begin()+i, hs ); + iIndexOut = i; + if( int(vHighScores.size()) > NUM_RANKING_LINES ) + vHighScores.erase( vHighScores.begin()+NUM_RANKING_LINES, vHighScores.end() ); + } +} + +// +// Helper function for reading/writing scores +// +bool FileRead(RageFile& f, CString& sOut) +{ + if (f.AtEOF()) + return false; + sOut = f.GetLine(); + return true; +} + +bool FileRead(RageFile& f, int& iOut) +{ + CString s; + if (!FileRead(f, s)) + return false; + iOut = atoi(s); + return true; +} + +bool FileRead(RageFile& f, unsigned& uOut) +{ + CString s; + if (!FileRead(f, s)) + return false; + uOut = atoi(s); + return true; +} + +bool FileRead(RageFile& f, float& fOut) +{ + CString s; + if (!FileRead(f, s)) + return false; + fOut = (float)atof(s); + return true; +} + +void FileWrite(RageFile& f, const CString& sWrite) +{ + f.PutLine( sWrite ); +} + +void FileWrite(RageFile& f, int iWrite) +{ + f.PutLine( ssprintf("%d", iWrite) ); +} + +void FileWrite(RageFile& f, size_t uWrite) +{ + f.PutLine( ssprintf("%lu", uWrite) ); +} + +void FileWrite(RageFile& f, float fWrite) +{ + f.PutLine( ssprintf("%f", fWrite) ); +} + +#define WARN_AND_RETURN { LOG->Warn("Error parsing file '%s' at %s:%d",fn.c_str(),__FILE__,__LINE__); return; } + + +void ProfileManager::ReadStepsMemCardDataFromFile( CString fn, int mc ) +{ + RageFile f(fn); + if (!f.IsOpen() || f.GetError() != 0) + return; /* don't warn if it just doesn't exist */ + + int version; + if( !FileRead(f, version) ) + WARN_AND_RETURN; + if( version != STEPS_MEM_CARD_DATA_VERSION ) + WARN_AND_RETURN; + + int iNumSongs; + if( !FileRead(f, iNumSongs) ) + WARN_AND_RETURN; + + for( int s=0; sGetSongFromDir( sSongDir ); + + int iNumNotes; + if( !FileRead(f, iNumNotes) ) + WARN_AND_RETURN; + + for( int n=0; nGetStepsByDescription( nt, sDescription ); + else + pNotes = pSong->GetStepsByDifficulty( nt, dc ); + } + + int iNumTimesPlayed; + if( !FileRead(f, iNumTimesPlayed) ) + WARN_AND_RETURN; + + if( pNotes ) + pNotes->m_MemCardDatas[mc].iNumTimesPlayed = iNumTimesPlayed; + + if( pNotes ) + pNotes->m_MemCardDatas[mc].vHighScores.resize(NUM_RANKING_LINES); + + for( int l=0; lm_MemCardDatas[mc].vHighScores[l].sName = sName; + pNotes->m_MemCardDatas[mc].vHighScores[l].grade = grade; + pNotes->m_MemCardDatas[mc].vHighScores[l].iScore = iScore; + pNotes->m_MemCardDatas[mc].vHighScores[l].fPercentDP = fPercentDP; + } + } + } + } +} + + +void ProfileManager::ReadCategoryRankingsFromFile( CString fn ) +{ + RageFile f(fn); + if (!f.IsOpen() || f.GetError() != 0) + return; /* don't warn if it just doesn't exist */ + + int version; + if( !FileRead(f, version) ) + WARN_AND_RETURN; + if( version != CATEGORY_RANKING_VERSION ) + WARN_AND_RETURN; + + for( int st=0; stGetCourseFromPath( sPath ); + if( pCourse == NULL ) + pCourse = SONGMAN->GetCourseFromName( sPath ); + + // even if we don't find the Course*, we still have to read past the input + + int NumStepsPlayed = 0; + if( !FileRead(f, NumStepsPlayed) ) + WARN_AND_RETURN; + + while( NumStepsPlayed-- ) + { + int st; + if( !FileRead(f, st) ) + WARN_AND_RETURN; + + int iNumTimesPlayed; + if( !FileRead(f, iNumTimesPlayed) ) + WARN_AND_RETURN; + + if( pCourse ) + pCourse->m_MemCardDatas[st][mc].iNumTimesPlayed = iNumTimesPlayed; + if( pCourse ) + pCourse->m_MemCardDatas[st][mc].vHighScores.resize(NUM_RANKING_LINES); + + for( int l=0; lm_MemCardDatas[st][mc].vHighScores[l].sName = sName; + pCourse->m_MemCardDatas[st][mc].vHighScores[l].iScore = iScore; + pCourse->m_MemCardDatas[st][mc].vHighScores[l].fSurviveTime = fSurviveTime; + } + } + } + } +} + +void ProfileManager::InitMachineScoresFromDisk() +{ + // read old style notes scores + ReadSM300NoteScores(); + + // category ranking + ReadCategoryRankingsFromFile( CATEGORY_RANKING_FILE ); + ReadCourseMemCardDataFromFile( MACHINE_COURSE_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); + ReadStepsMemCardDataFromFile( MACHINE_STEPS_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); +} + +void ProfileManager::ReadSM300NoteScores() +{ + IniFile ini; + ini.SetPath( SM_300_STATISTICS_FILE ); + if( !ini.ReadFile() ) { + LOG->Trace( "WARNING: Could not read SM 3.0 final statistics '%s'.", SM_300_STATISTICS_FILE ); + return; // load nothing + } + + // load song statistics + const IniFile::key* pKey = ini.GetKey( "Statistics" ); + if( pKey ) + { + for( IniFile::key::const_iterator iter = pKey->begin(); + iter != pKey->end(); + iter++ ) + { + CString name = iter->first; + CString value = iter->second; + + // Each value has the format "SongName::StepsType::StepsDescription=TimesPlayed::TopGrade::TopScore::MaxCombo". + char szSongDir[256]; + char szStepsType[256]; + char szStepsDescription[256]; + int iRetVal; + + // Parse for Song name and Notes name + iRetVal = sscanf( name, "%[^:]::%[^:]::%[^:]", szSongDir, szStepsType, szStepsDescription ); + if( iRetVal != 3 ) + continue; // this line doesn't match what is expected + + CString sSongDir = FixSlashes( szSongDir ); + + // Search for the corresponding Song pointer. + Song* pSong = SONGMAN->GetSongFromDir( sSongDir ); + if( pSong == NULL ) // didn't find a match + continue; // skip this entry + + StepsType st = GAMEMAN->StringToNotesType( szStepsType ); + Difficulty dc = StringToDifficulty( szStepsDescription ); + + // Search for the corresponding Notes pointer. + Steps* pNotes = pSong->GetStepsByDifficulty( st, dc ); + if( pNotes == NULL ) // didn't find a match + continue; // skip this entry + + + // Parse the Notes statistics. + char szGradeLetters[10]; // longest possible string is "AAA" + int iMaxCombo; // throw away + + pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].vHighScores.resize(1); + + iRetVal = sscanf( + value, + "%d::%[^:]::%d::%d", + &pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].iNumTimesPlayed, + szGradeLetters, + &pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].vHighScores[0].iScore, + &iMaxCombo + ); + if( iRetVal != 4 ) + continue; + + pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].vHighScores[0].grade = StringToGrade( szGradeLetters ); + } + } +} + + +void ProfileManager::SaveCategoryRankingsToFile( CString fn ) +{ + LOG->Trace("SongManager::SaveCategoryRankingsToFile"); + + RageFile f(fn); + if (!f.IsOpen() || f.GetError() != 0) + return; + + FileWrite( f, CATEGORY_RANKING_VERSION ); + + for( int st=0; stTrace("SongManager::SaveCourseMemCardDataToFile"); + + RageFile f(fn); + if (!f.IsOpen() || f.GetError() != 0) + return; + + FileWrite( f, COURSE_MEM_CARD_DATA_VERSION ); + + vector vpCourses; + SONGMAN->GetAllCourses( vpCourses, true ); + + FileWrite( f, vpCourses.size() ); + + for( unsigned c=0; cm_bIsAutogen ) + FileWrite( f, pCourse->m_sName ); + else + FileWrite( f, pCourse->m_sPath ); + + int NumStepsPlayed = 0; + int st; + for( st=0; stm_MemCardDatas[st][mc].iNumTimesPlayed ) + ++NumStepsPlayed; + FileWrite( f, NumStepsPlayed ); + + for( st=0; stm_MemCardDatas[st][mc].iNumTimesPlayed ) + continue; + --NumStepsPlayed; + + FileWrite( f, st ); + FileWrite( f, pCourse->m_MemCardDatas[st][mc].iNumTimesPlayed ); + pCourse->m_MemCardDatas[st][mc].vHighScores.resize(NUM_RANKING_LINES); + for( int l=0; lm_MemCardDatas[st][mc].vHighScores[l].sName) ) + pCourse->m_MemCardDatas[st][mc].vHighScores[l].sName = ""; + + FileWrite( f, pCourse->m_MemCardDatas[st][mc].vHighScores[l].sName ); + FileWrite( f, pCourse->m_MemCardDatas[st][mc].vHighScores[l].iScore ); + FileWrite( f, pCourse->m_MemCardDatas[st][mc].vHighScores[l].fSurviveTime ); + } + } + ASSERT( !NumStepsPlayed ); + } +} + +void ProfileManager::SaveStepsMemCardDataToFile( CString fn, int mc ) +{ + LOG->Trace("SongManager::SaveStepsMemCardDataToFile %s", fn.c_str()); + + RageFile f(fn); + if (!f.IsOpen() || f.GetError() != 0) + return; + + FileWrite( f, STEPS_MEM_CARD_DATA_VERSION ); + + const vector &vpSongs = SONGMAN->GetAllSongs(); + + FileWrite( f, vpSongs.size() ); + + for( unsigned s=0; s vNotes; + for( unsigned i=0; im_apNotes.size(); ++i ) + { + Steps* pNotes = pSong->m_apNotes[i]; + if( !pNotes->m_MemCardDatas[mc].iNumTimesPlayed ) + continue; + vNotes.push_back( pNotes ); + } + + FileWrite( f, pSong->GetSongDir() ); + FileWrite( f, vNotes.size() ); + + if( vNotes.size() == 0 ) + continue; // skip + + for( unsigned n=0; nm_StepsType ); + FileWrite( f, pNotes->GetDifficulty() ); + FileWrite( f, pNotes->GetDescription() ); + FileWrite( f, pNotes->m_MemCardDatas[mc].iNumTimesPlayed ); + + pNotes->m_MemCardDatas[mc].vHighScores.resize(NUM_RANKING_LINES); + for( int l=0; lm_MemCardDatas[mc].vHighScores[l].sName) ) + pNotes->m_MemCardDatas[mc].vHighScores[l].sName = ""; + + FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].sName ); + FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].grade ); + FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].iScore ); + FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].fPercentDP ); + } + } + } +} + +static void HTMLWritePerGameHeader( RageFile &f, Game game ) +{ + const GameDef* pGameDef = GAMEMAN->GetGameDefForGame(game); + + vector aStepsTypes; + GAMEMAN->GetNotesTypesForGame( game, aStepsTypes ); + + f.PutLine( ssprintf("

%s

", pGameDef->m_szName) ); + + f.PutLine( "" ); + f.Write( "" ); + + unsigned j; + for( j=0; j%s", NUM_DIFFICULTIES, GAMEMAN->NotesTypeToString(st).c_str()) ); + } + f.PutLine( "" ); + + f.Write( "" ); + for( j=0; j%s", Capitalize(DifficultyToString(d).Left(3)).c_str()) ); + } + } + + f.PutLine( "" ); +} + +static CString HTMLQuoteDoubleQuotes( CString str ) +{ + str.Replace( "\"", """ ); + return str; +} + +void ProfileManager::WriteStatsWebPage() +{ + RageFile f; + if( !f.Open( STATS_PATH, RageFile::WRITE ) ) + return; + + f.PutLine( "" ); + f.PutLine( "" ); + f.PutLine( "" ); + f.PutLine( ssprintf("%s", PRODUCT_NAME_VER) ); + f.PutLine( "" ); + f.PutLine( "" ); + + vector vpSongs = SONGMAN->GetAllSongs(); + SortSongPointerArrayByGroupAndTitle( vpSongs ); + + // + // Print song list + // + f.PutLine( "
title
 
" ); + for( unsigned i=0; i" ); + /* XXX: We can't call pSong->HasBanner on every song; it'll effectively re-traverse the entire + * song directory tree checking if each banner file really exists. + * + * (Note for testing this: remember that we'll cache directories for a time; this is only slow if + * the directory cache expires before we get here.) */ + //CString sImagePath = pSong->HasBanner() ? pSong->GetBannerPath() : (pSong->HasBackground() ? pSong->GetBackgroundPath() : "" ); + CString sImagePath = pSong->GetBannerPath(); + if( sImagePath.empty() ) + f.Write( "" ); + else + f.Write( ssprintf("", HTMLQuoteDoubleQuotes(sImagePath).c_str()) ); + + f.Write( ssprintf("", pSong->GetTranslitArtist().c_str()) ); + f.PutLine( "" ); + } + f.PutLine( "
%s
", pSong->GetTranslitMainTitle().c_str()) ); + f.Write( ssprintf("%s
", pSong->GetTranslitSubTitle().c_str()) ); + f.Write( ssprintf("%s
\n
" ); + + + // + // Print steps tables + // + for( int g=0; g aStepsTypes; + GAMEMAN->GetNotesTypesForGame( game, aStepsTypes ); + + bool WroteHeader = false; + for( unsigned i=0; i Steps; + unsigned j; + for( j=0; j < aStepsTypes.size(); j++ ) + pSong->GetSteps( Steps, (StepsType) aStepsTypes[j], DIFFICULTY_INVALID, -1, -1, "", false ); + + /* Don't write anything for songs that have no steps at all for this + * game. Otherwise, we'll write pages and pages of empty fields for + * all of the less-used game types. */ + if( Steps.size() == 0 ) + continue; // skip + + /* We have some steps for this game. Make sure we've written the game header. */ + if( !WroteHeader ) + { + HTMLWritePerGameHeader( f, game ); + WroteHeader = true; + } + + + f.PutLine( "" ); + + f.Write( ssprintf("%s", pSong->GetTranslitMainTitle().c_str()) ); + + SortStepsByTypeAndDifficulty( Steps ); + + unsigned CurSteps = 0; + for( j=0; jm_StepsType == aStepsTypes[j] && + Steps[CurSteps]->GetDifficulty() == k ) + { + f.PutLine( ssprintf("%d", Steps[CurSteps]->GetMeter()) ); + ++CurSteps; + } + else + f.PutLine( " " ); + } + } + + f.Write( "" ); + } + if( WroteHeader ) + f.PutLine( "\n
" ); // footer + } + + f.PutLine( "" ); + f.PutLine( "" ); +} diff --git a/stepmania/src/ProfileManager.h b/stepmania/src/ProfileManager.h index 2cfd66ce38..841afc5749 100644 --- a/stepmania/src/ProfileManager.h +++ b/stepmania/src/ProfileManager.h @@ -14,6 +14,7 @@ #include "PlayerNumber.h" +#include "GameConstantsAndTypes.h" struct Profile { @@ -54,12 +55,60 @@ public: bool SaveProfile( PlayerNumber pn ); void UnloadProfile( PlayerNumber pn ); + void SaveMachineProfile(); + bool IsUsingProfile( PlayerNumber pn ) { return !m_sProfileDir[pn].empty(); } Profile* GetProfile( PlayerNumber pn ); + Profile* GetMachineProfile() { return &m_MachineProfile; } bool IsUsingMemoryCard( PlayerNumber pn ) { return m_bUsingMemoryCard[pn]; } + + // + // High scores + // + void InitMachineScoresFromDisk(); + void SaveMachineScoresToDisk(); + + struct CategoryData + { + struct HighScore + { + int iScore; + CString sName; + + HighScore() + { + iScore = 0; + } + + bool operator>=( const HighScore& other ) const + { + return iScore >= other.iScore; + } + }; + vector vHighScores; + + void AddHighScore( HighScore hs, int &iIndexOut ); + + } m_CategoryDatas[NUM_STEPS_TYPES][NUM_RANKING_CATEGORIES]; + + void AddHighScore( StepsType nt, RankingCategory rc, PlayerNumber pn, CategoryData::HighScore hs, int &iMachineIndexOut ) + { + hs.sName = RANKING_TO_FILL_IN_MARKER[pn]; + m_CategoryDatas[nt][rc].AddHighScore( hs, iMachineIndexOut ); + } + + void ReadSM300NoteScores(); + void ReadStepsMemCardDataFromFile( CString fn, int c ); + void ReadCourseMemCardDataFromFile( CString fn, int c ); + void ReadCategoryRankingsFromFile( CString fn ); + + void SaveStepsMemCardDataToFile( CString fn, int c ); + void SaveCourseMemCardDataToFile( CString fn, int c ); + void SaveCategoryRankingsToFile( CString fn ); + private: bool LoadDefaultProfileFromMachine( PlayerNumber pn ); bool LoadProfileFromMemoryCard( PlayerNumber pn ); @@ -74,6 +123,10 @@ private: // actual loaded profile data Profile m_Profile[NUM_PLAYERS]; + + Profile m_MachineProfile; + + void WriteStatsWebPage(); }; diff --git a/stepmania/src/ScreenCredits.cpp b/stepmania/src/ScreenCredits.cpp index e7d5ed8c7a..bb1b3774c4 100644 --- a/stepmania/src/ScreenCredits.cpp +++ b/stepmania/src/ScreenCredits.cpp @@ -258,9 +258,5 @@ ScreenCredits::~ScreenCredits() void ScreenCredits::HandleScreenMessage( const ScreenMessage SM ) { - /* XXX: is this needed anymore? */ - if( SM == SM_GoToNextScreen ) - SONGMAN->SaveMachineScoresToDisk(); - ScreenAttract::HandleScreenMessage( SM ); } diff --git a/stepmania/src/ScreenEvaluation.cpp b/stepmania/src/ScreenEvaluation.cpp index 42066e9ae3..a5829f36eb 100644 --- a/stepmania/src/ScreenEvaluation.cpp +++ b/stepmania/src/ScreenEvaluation.cpp @@ -29,6 +29,7 @@ #include "UnlockSystem.h" #include "Course.h" #include "LightsManager.h" +#include "ProfileManager.h" const int NUM_SCORE_DIGITS = 9; @@ -250,9 +251,9 @@ ScreenEvaluation::ScreenEvaluation( CString sClassName ) : Screen(sClassName) float fAverageMeter = stageStats.iMeter[p] / (float)PREFSMAN->m_iNumArcadeStages; rc[p] = AverageMeterToRankingCategory( fAverageMeter ); - SongManager::CategoryData::HighScore hs; + ProfileManager::CategoryData::HighScore hs; hs.iScore = stageStats.iScore[p]; - SONGMAN->AddHighScore( nt, rc[p], (PlayerNumber)p, hs, iMachineHighScoreIndex[p] ); + PROFILEMAN->AddHighScore( nt, rc[p], (PlayerNumber)p, hs, iMachineHighScoreIndex[p] ); // If unlocking is enabled, save the dance points if( PREFSMAN->m_bUseUnlockSystem ) diff --git a/stepmania/src/ScreenMusicScroll.cpp b/stepmania/src/ScreenMusicScroll.cpp index 2c79c56647..6cfb1782d3 100644 --- a/stepmania/src/ScreenMusicScroll.cpp +++ b/stepmania/src/ScreenMusicScroll.cpp @@ -100,9 +100,5 @@ void ScreenMusicScroll::Update( float fDeltaTime ) void ScreenMusicScroll::HandleScreenMessage( const ScreenMessage SM ) { - /* XXX: is this needed anymore? */ - if( SM == SM_GoToNextScreen ) - SONGMAN->SaveMachineScoresToDisk(); - ScreenAttract::HandleScreenMessage( SM ); } diff --git a/stepmania/src/ScreenNameEntryTraditional.cpp b/stepmania/src/ScreenNameEntryTraditional.cpp index b27d96687b..6af59cad70 100644 --- a/stepmania/src/ScreenNameEntryTraditional.cpp +++ b/stepmania/src/ScreenNameEntryTraditional.cpp @@ -89,10 +89,10 @@ ScreenNameEntryTraditional::ScreenNameEntryTraditional( CString sClassName ) : S if( i == 0 ) { - SongManager::CategoryData::HighScore hs; + ProfileManager::CategoryData::HighScore hs; hs.iScore = 1234567; StepsType nt = GAMESTATE->GetCurrentStyleDef()->m_StepsType; - SONGMAN->AddHighScore( nt, RANKING_A, (PlayerNumber)i, hs, a ); + PROFILEMAN->AddHighScore( nt, RANKING_A, (PlayerNumber)i, hs, a ); } } diff --git a/stepmania/src/ScreenRanking.cpp b/stepmania/src/ScreenRanking.cpp index 0fc765505b..9695315098 100644 --- a/stepmania/src/ScreenRanking.cpp +++ b/stepmania/src/ScreenRanking.cpp @@ -21,6 +21,7 @@ #include "NoteData.h" #include "NoteDataUtil.h" #include "ActorUtil.h" +#include "ProfileManager.h" static int g_iLastSongShown = 0; @@ -419,9 +420,9 @@ void ScreenRanking::SetPage( PageToShow pts ) for( int l=0; lm_CategoryDatas[pts.nt][pts.category].vHighScores.size() ) - hs = SONGMAN->m_CategoryDatas[pts.nt][pts.category].vHighScores[l]; + ProfileManager::CategoryData::HighScore hs; + if( l < (int)PROFILEMAN->m_CategoryDatas[pts.nt][pts.category].vHighScores.size() ) + hs = PROFILEMAN->m_CategoryDatas[pts.nt][pts.category].vHighScores[l]; if( hs.sName.empty() ) hs.sName = EMPTY_SCORE_NAME; diff --git a/stepmania/src/ScreenTitleMenu.cpp b/stepmania/src/ScreenTitleMenu.cpp index 52d33a5331..5cd3cd821a 100644 --- a/stepmania/src/ScreenTitleMenu.cpp +++ b/stepmania/src/ScreenTitleMenu.cpp @@ -30,6 +30,8 @@ #include "UnlockSystem.h" #include "ProductInfo.h" #include "LightsManager.h" +#include "Bookkeeper.h" +#include "ProfileManager.h" #define LOGO_ON_COMMAND THEME->GetMetric("ScreenTitleMenu","LogoOnCommand") @@ -71,6 +73,12 @@ ScreenTitleMenu::ScreenTitleMenu( CString sClassName ) : ScreenSelect( sClassNam return; } + + // HACK: save stats intermitently in case of crash + BOOKKEEPER->WriteToDisk(); + PROFILEMAN->SaveMachineScoresToDisk(); + + /* XXX We really need two common calls: 1, something run when exiting from gameplay * (to do this reset), and 2, something run when entering gameplay, to apply default * options. Having special cases in attract screens and the title menu to reset diff --git a/stepmania/src/SongManager.cpp b/stepmania/src/SongManager.cpp index 3785189b19..9355a05788 100644 --- a/stepmania/src/SongManager.cpp +++ b/stepmania/src/SongManager.cpp @@ -28,22 +28,14 @@ #include "ThemeManager.h" #include "GameManager.h" #include "RageFile.h" -#include "ProductInfo.h" #include "RageTextureManager.h" #include "Banner.h" +#include "ProfileManager.h" SongManager* SONGMAN = NULL; // global and accessable from anywhere in our program -#define SM_300_STATISTICS_FILE BASE_PATH "statistics.ini" #define SONGS_DIR BASE_PATH "Songs" SLASH #define COURSES_DIR BASE_PATH "Courses" SLASH -#define STATS_PATH BASE_PATH "stats.html" -const CString CATEGORY_RANKING_FILE = BASE_PATH "Data" SLASH "CategoryRanking.dat"; -const CString MACHINE_STEPS_MEM_CARD_DATA = BASE_PATH "Data" SLASH "MachineStepsMemCardData.dat"; -const CString MACHINE_COURSE_MEM_CARD_DATA = BASE_PATH "Data" SLASH "MachineCourseMemCardData.dat"; -const int CATEGORY_RANKING_VERSION = 3; -const int STEPS_MEM_CARD_DATA_VERSION = 7; -const int COURSE_MEM_CARD_DATA_VERSION = 5; #define NUM_GROUP_COLORS THEME->GetMetricI("SongManager","NumGroupColors") @@ -79,7 +71,6 @@ SongManager::SongManager( LoadingWindow *ld ) InitSongsFromDisk( ld ); InitCoursesFromDisk( ld ); InitAutogenCourses(); - InitMachineScoresFromDisk(); } catch(...) { SONGMAN = NULL; @@ -92,36 +83,14 @@ SongManager::SongManager( LoadingWindow *ld ) SongManager::~SongManager() { - SaveMachineScoresToDisk(); - - WriteStatsWebPage(); - FreeSongs(); } -void SongManager::CategoryData::AddHighScore( HighScore hs, int &iIndexOut ) -{ - int i; - for( i=0; i<(int)vHighScores.size(); i++ ) - { - if( hs >= vHighScores[i] ) - break; - } - if( i < NUM_RANKING_LINES ) - { - vHighScores.insert( vHighScores.begin()+i, hs ); - iIndexOut = i; - if( int(vHighScores.size()) > NUM_RANKING_LINES ) - vHighScores.erase( vHighScores.begin()+NUM_RANKING_LINES, vHighScores.end() ); - } -} - - void SongManager::Reload() { FlushDirCache(); - SaveMachineScoresToDisk(); + PROFILEMAN->SaveMachineScoresToDisk(); FreeSongs(); FreeCourses(); @@ -131,15 +100,7 @@ void SongManager::Reload() InitSongsFromDisk(NULL); InitCoursesFromDisk(NULL); InitAutogenCourses(); - InitMachineScoresFromDisk(); -} - - -void SongManager::SaveMachineScoresToDisk() -{ - SaveCategoryRankingsToFile( CATEGORY_RANKING_FILE ); - SaveStepsMemCardDataToFile( MACHINE_STEPS_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); - SaveCourseMemCardDataToFile( MACHINE_COURSE_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); + PROFILEMAN->InitMachineScoresFromDisk(); } void SongManager::InitSongsFromDisk( LoadingWindow *ld ) @@ -335,490 +296,6 @@ void SongManager::FreeSongs() m_sGroupBannerPaths.clear(); } - -// -// Helper function for reading/writing scores -// -bool FileRead(RageFile& f, CString& sOut) -{ - if (f.AtEOF()) - return false; - sOut = f.GetLine(); - return true; -} - -bool FileRead(RageFile& f, int& iOut) -{ - CString s; - if (!FileRead(f, s)) - return false; - iOut = atoi(s); - return true; -} - -bool FileRead(RageFile& f, unsigned& uOut) -{ - CString s; - if (!FileRead(f, s)) - return false; - uOut = atoi(s); - return true; -} - -bool FileRead(RageFile& f, float& fOut) -{ - CString s; - if (!FileRead(f, s)) - return false; - fOut = (float)atof(s); - return true; -} - -void FileWrite(RageFile& f, const CString& sWrite) -{ - f.PutLine( sWrite ); -} - -void FileWrite(RageFile& f, int iWrite) -{ - f.PutLine( ssprintf("%d", iWrite) ); -} - -void FileWrite(RageFile& f, size_t uWrite) -{ - f.PutLine( ssprintf("%lu", uWrite) ); -} - -void FileWrite(RageFile& f, float fWrite) -{ - f.PutLine( ssprintf("%f", fWrite) ); -} - -#define WARN_AND_RETURN { LOG->Warn("Error parsing file '%s' at %s:%d",fn.c_str(),__FILE__,__LINE__); return; } - -void SongManager::ReadStepsMemCardDataFromFile( CString fn, int mc ) -{ - RageFile f(fn); - if (!f.IsOpen() || f.GetError() != 0) - return; /* don't warn if it just doesn't exist */ - - int version; - if( !FileRead(f, version) ) - WARN_AND_RETURN; - if( version != STEPS_MEM_CARD_DATA_VERSION ) - WARN_AND_RETURN; - - int iNumSongs; - if( !FileRead(f, iNumSongs) ) - WARN_AND_RETURN; - - for( int s=0; sGetSongFromDir( sSongDir ); - - int iNumNotes; - if( !FileRead(f, iNumNotes) ) - WARN_AND_RETURN; - - for( int n=0; nGetStepsByDescription( nt, sDescription ); - else - pNotes = pSong->GetStepsByDifficulty( nt, dc ); - } - - int iNumTimesPlayed; - if( !FileRead(f, iNumTimesPlayed) ) - WARN_AND_RETURN; - - if( pNotes ) - pNotes->m_MemCardDatas[mc].iNumTimesPlayed = iNumTimesPlayed; - - if( pNotes ) - pNotes->m_MemCardDatas[mc].vHighScores.resize(NUM_RANKING_LINES); - - for( int l=0; lm_MemCardDatas[mc].vHighScores[l].sName = sName; - pNotes->m_MemCardDatas[mc].vHighScores[l].grade = grade; - pNotes->m_MemCardDatas[mc].vHighScores[l].iScore = iScore; - pNotes->m_MemCardDatas[mc].vHighScores[l].fPercentDP = fPercentDP; - } - } - } - } -} - - -void SongManager::ReadCategoryRankingsFromFile( CString fn ) -{ - RageFile f(fn); - if (!f.IsOpen() || f.GetError() != 0) - return; /* don't warn if it just doesn't exist */ - - int version; - if( !FileRead(f, version) ) - WARN_AND_RETURN; - if( version != CATEGORY_RANKING_VERSION ) - WARN_AND_RETURN; - - for( int st=0; stm_MemCardDatas[st][mc].iNumTimesPlayed = iNumTimesPlayed; - if( pCourse ) - pCourse->m_MemCardDatas[st][mc].vHighScores.resize(NUM_RANKING_LINES); - - for( int l=0; lm_MemCardDatas[st][mc].vHighScores[l].sName = sName; - pCourse->m_MemCardDatas[st][mc].vHighScores[l].iScore = iScore; - pCourse->m_MemCardDatas[st][mc].vHighScores[l].fSurviveTime = fSurviveTime; - } - } - } - } -} - -void SongManager::InitMachineScoresFromDisk() -{ - // read old style notes scores - ReadSM300NoteScores(); - - // category ranking - ReadCategoryRankingsFromFile( CATEGORY_RANKING_FILE ); - ReadCourseMemCardDataFromFile( MACHINE_COURSE_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); - ReadStepsMemCardDataFromFile( MACHINE_STEPS_MEM_CARD_DATA, MEMORY_CARD_MACHINE ); -} - -void SongManager::ReadSM300NoteScores() -{ - IniFile ini; - ini.SetPath( SM_300_STATISTICS_FILE ); - if( !ini.ReadFile() ) { - LOG->Trace( "WARNING: Could not read SM 3.0 final statistics '%s'.", SM_300_STATISTICS_FILE ); - return; // load nothing - } - - // load song statistics - const IniFile::key* pKey = ini.GetKey( "Statistics" ); - if( pKey ) - { - for( IniFile::key::const_iterator iter = pKey->begin(); - iter != pKey->end(); - iter++ ) - { - CString name = iter->first; - CString value = iter->second; - - // Each value has the format "SongName::StepsType::StepsDescription=TimesPlayed::TopGrade::TopScore::MaxCombo". - char szSongDir[256]; - char szStepsType[256]; - char szStepsDescription[256]; - int iRetVal; - - // Parse for Song name and Notes name - iRetVal = sscanf( name, "%[^:]::%[^:]::%[^:]", szSongDir, szStepsType, szStepsDescription ); - if( iRetVal != 3 ) - continue; // this line doesn't match what is expected - - CString sSongDir = FixSlashes( szSongDir ); - - // Search for the corresponding Song pointer. - Song* pSong = GetSongFromDir( sSongDir ); - if( pSong == NULL ) // didn't find a match - continue; // skip this entry - - StepsType st = GAMEMAN->StringToNotesType( szStepsType ); - Difficulty dc = StringToDifficulty( szStepsDescription ); - - // Search for the corresponding Notes pointer. - Steps* pNotes = pSong->GetStepsByDifficulty( st, dc ); - if( pNotes == NULL ) // didn't find a match - continue; // skip this entry - - - // Parse the Notes statistics. - char szGradeLetters[10]; // longest possible string is "AAA" - int iMaxCombo; // throw away - - pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].vHighScores.resize(1); - - iRetVal = sscanf( - value, - "%d::%[^:]::%d::%d", - &pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].iNumTimesPlayed, - szGradeLetters, - &pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].vHighScores[0].iScore, - &iMaxCombo - ); - if( iRetVal != 4 ) - continue; - - pNotes->m_MemCardDatas[MEMORY_CARD_MACHINE].vHighScores[0].grade = StringToGrade( szGradeLetters ); - } - } -} - - -void SongManager::SaveCategoryRankingsToFile( CString fn ) -{ - LOG->Trace("SongManager::SaveCategoryRankingsToFile"); - - RageFile f(fn); - if (!f.IsOpen() || f.GetError() != 0) - return; - - FileWrite( f, CATEGORY_RANKING_VERSION ); - - for( int st=0; stTrace("SongManager::SaveCourseMemCardDataToFile"); - - RageFile f(fn); - if (!f.IsOpen() || f.GetError() != 0) - return; - - FileWrite( f, COURSE_MEM_CARD_DATA_VERSION ); - - FileWrite( f, m_pCourses.size() ); - - for( unsigned c=0; cm_bIsAutogen ) - FileWrite( f, pCourse->m_sName ); - else - FileWrite( f, pCourse->m_sPath ); - - int NumStepsPlayed = 0; - int st; - for( st=0; stm_MemCardDatas[st][mc].iNumTimesPlayed ) - ++NumStepsPlayed; - FileWrite( f, NumStepsPlayed ); - - for( st=0; stm_MemCardDatas[st][mc].iNumTimesPlayed ) - continue; - --NumStepsPlayed; - - FileWrite( f, st ); - FileWrite( f, pCourse->m_MemCardDatas[st][mc].iNumTimesPlayed ); - pCourse->m_MemCardDatas[st][mc].vHighScores.resize(NUM_RANKING_LINES); - for( int l=0; lm_MemCardDatas[st][mc].vHighScores[l].sName) ) - pCourse->m_MemCardDatas[st][mc].vHighScores[l].sName = ""; - - FileWrite( f, pCourse->m_MemCardDatas[st][mc].vHighScores[l].sName ); - FileWrite( f, pCourse->m_MemCardDatas[st][mc].vHighScores[l].iScore ); - FileWrite( f, pCourse->m_MemCardDatas[st][mc].vHighScores[l].fSurviveTime ); - } - } - ASSERT( !NumStepsPlayed ); - } -} - -void SongManager::SaveStepsMemCardDataToFile( CString fn, int mc ) -{ - LOG->Trace("SongManager::SaveStepsMemCardDataToFile %s", fn.c_str()); - - RageFile f(fn); - if (!f.IsOpen() || f.GetError() != 0) - return; - - FileWrite( f, STEPS_MEM_CARD_DATA_VERSION ); - - FileWrite( f, m_pSongs.size() ); - - for( unsigned s=0; s vNotes; - for( unsigned i=0; im_apNotes.size(); ++i ) - { - Steps* pNotes = pSong->m_apNotes[i]; - if( !pNotes->m_MemCardDatas[mc].iNumTimesPlayed ) - continue; - vNotes.push_back( pNotes ); - } - - FileWrite( f, pSong->GetSongDir() ); - FileWrite( f, vNotes.size() ); - - if( vNotes.size() == 0 ) - continue; // skip - - for( unsigned n=0; nm_StepsType ); - FileWrite( f, pNotes->GetDifficulty() ); - FileWrite( f, pNotes->GetDescription() ); - FileWrite( f, pNotes->m_MemCardDatas[mc].iNumTimesPlayed ); - - pNotes->m_MemCardDatas[mc].vHighScores.resize(NUM_RANKING_LINES); - for( int l=0; lm_MemCardDatas[mc].vHighScores[l].sName) ) - pNotes->m_MemCardDatas[mc].vHighScores[l].sName = ""; - - FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].sName ); - FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].grade ); - FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].iScore ); - FileWrite( f, pNotes->m_MemCardDatas[mc].vHighScores[l].fPercentDP ); - } - } - } -} - - CString SongManager::GetGroupBannerPath( CString sGroupName ) { unsigned i; @@ -1390,168 +867,3 @@ void SongManager::UpdateRankingCourses() } } -static CString HTMLQuoteDoubleQuotes( CString str ) -{ - str.Replace( "\"", """ ); - return str; -} - -static bool CompareStepsPointersByTypeAndDifficulty(const Steps *pStep1, const Steps *pStep2) -{ - if( pStep1->m_StepsType < pStep2->m_StepsType ) - return true; - if( pStep1->m_StepsType > pStep2->m_StepsType ) - return false; - return pStep1->GetDifficulty() < pStep2->GetDifficulty(); -} - -static void SortStepsByTypeAndDifficulty( vector &arraySongPointers ) -{ - sort( arraySongPointers.begin(), arraySongPointers.end(), CompareStepsPointersByTypeAndDifficulty ); -} - -static void HTMLWritePerGameHeader( RageFile &f, Game game ) -{ - const GameDef* pGameDef = GAMEMAN->GetGameDefForGame(game); - - vector aStepsTypes; - GAMEMAN->GetNotesTypesForGame( game, aStepsTypes ); - - f.PutLine( ssprintf("

%s

", pGameDef->m_szName) ); - - f.PutLine( "" ); - f.Write( "" ); - - unsigned j; - for( j=0; j%s", NUM_DIFFICULTIES, GAMEMAN->NotesTypeToString(st).c_str()) ); - } - f.PutLine( "" ); - - f.Write( "" ); - for( j=0; j%s", Capitalize(DifficultyToString(d).Left(3)).c_str()) ); - } - } - - f.PutLine( "" ); -} - -// TODO: Move this to a different file. No need to clutter SongManager. -void SongManager::WriteStatsWebPage() -{ - RageFile f; - if( !f.Open( STATS_PATH, RageFile::WRITE ) ) - return; - - f.PutLine( "" ); - f.PutLine( "" ); - f.PutLine( "" ); - f.PutLine( ssprintf("%s", PRODUCT_NAME_VER) ); - f.PutLine( "" ); - f.PutLine( "" ); - - vector vSongs = m_pSongs; - SortSongPointerArrayByGroupAndTitle( vSongs ); - - // - // Print song list - // - f.PutLine( "
title
 
" ); - for( unsigned i=0; i" ); - /* XXX: We can't call pSong->HasBanner on every song; it'll effectively re-traverse the entire - * song directory tree checking if each banner file really exists. - * - * (Note for testing this: remember that we'll cache directories for a time; this is only slow if - * the directory cache expires before we get here.) */ - //CString sImagePath = pSong->HasBanner() ? pSong->GetBannerPath() : (pSong->HasBackground() ? pSong->GetBackgroundPath() : "" ); - CString sImagePath = pSong->GetBannerPath(); - if( sImagePath.empty() ) - f.Write( "" ); - else - f.Write( ssprintf("", HTMLQuoteDoubleQuotes(sImagePath).c_str()) ); - - f.Write( ssprintf("", pSong->GetTranslitArtist().c_str()) ); - f.PutLine( "" ); - } - f.PutLine( "
%s
", pSong->GetTranslitMainTitle().c_str()) ); - f.Write( ssprintf("%s
", pSong->GetTranslitSubTitle().c_str()) ); - f.Write( ssprintf("%s
\n
" ); - - - // - // Print steps tables - // - for( int g=0; g aStepsTypes; - GAMEMAN->GetNotesTypesForGame( game, aStepsTypes ); - - bool WroteHeader = false; - for( unsigned i=0; i Steps; - unsigned j; - for( j=0; j < aStepsTypes.size(); j++ ) - pSong->GetSteps( Steps, (StepsType) aStepsTypes[j], DIFFICULTY_INVALID, -1, -1, "", false ); - - /* Don't write anything for songs that have no steps at all for this - * game. Otherwise, we'll write pages and pages of empty fields for - * all of the less-used game types. */ - if( Steps.size() == 0 ) - continue; // skip - - /* We have some steps for this game. Make sure we've written the game header. */ - if( !WroteHeader ) - { - HTMLWritePerGameHeader( f, game ); - WroteHeader = true; - } - - - f.PutLine( "" ); - - f.Write( ssprintf("%s", pSong->GetTranslitMainTitle().c_str()) ); - - SortStepsByTypeAndDifficulty( Steps ); - - unsigned CurSteps = 0; - for( j=0; jm_StepsType == aStepsTypes[j] && - Steps[CurSteps]->GetDifficulty() == k ) - { - f.PutLine( ssprintf("%d", Steps[CurSteps]->GetMeter()) ); - ++CurSteps; - } - else - f.PutLine( " " ); - } - } - - f.Write( "" ); - } - if( WroteHeader ) - f.PutLine( "\n
" ); // footer - } - - f.PutLine( "" ); - f.PutLine( "" ); -} diff --git a/stepmania/src/SongManager.h b/stepmania/src/SongManager.h index ca34aa6613..03503faceb 100644 --- a/stepmania/src/SongManager.h +++ b/stepmania/src/SongManager.h @@ -86,53 +86,9 @@ public: Course* GetCourseFromName( CString sName ); - // - // High scores - // - void InitMachineScoresFromDisk(); - void SaveMachineScoresToDisk(); + void UpdateBest(); // update Players Best - struct CategoryData - { - struct HighScore - { - int iScore; - CString sName; - - HighScore() - { - iScore = 0; - } - - bool operator>=( const HighScore& other ) const - { - return iScore >= other.iScore; - } - }; - vector vHighScores; - - void AddHighScore( HighScore hs, int &iIndexOut ); - - } m_CategoryDatas[NUM_STEPS_TYPES][NUM_RANKING_CATEGORIES]; - - void AddHighScore( StepsType nt, RankingCategory rc, PlayerNumber pn, CategoryData::HighScore hs, int &iMachineIndexOut ) - { - hs.sName = RANKING_TO_FILL_IN_MARKER[pn]; - m_CategoryDatas[nt][rc].AddHighScore( hs, iMachineIndexOut ); - } - - void UpdateBest(); - - void UpdateRankingCourses(); - - void ReadSM300NoteScores(); - void ReadStepsMemCardDataFromFile( CString fn, int c ); - void ReadCourseMemCardDataFromFile( CString fn, int c ); - void ReadCategoryRankingsFromFile( CString fn ); - - void SaveStepsMemCardDataToFile( CString fn, int c ); - void SaveCourseMemCardDataToFile( CString fn, int c ); - void SaveCategoryRankingsToFile( CString fn ); + void UpdateRankingCourses(); // courses shown on the ranking screen protected: void LoadStepManiaSongDir( CString sDir, LoadingWindow *ld ); @@ -144,8 +100,6 @@ protected: Song *FindSong( CString sGroup, CString sSong ); - void WriteStatsWebPage(); - vector m_pSongs; // all songs that can be played vector m_pBestSongs[NUM_MEMORY_CARDS]; CStringArray m_sGroupNames; diff --git a/stepmania/src/StepMania.cpp b/stepmania/src/StepMania.cpp index 662bc02659..9d0305e2fb 100644 --- a/stepmania/src/StepMania.cpp +++ b/stepmania/src/StepMania.cpp @@ -942,6 +942,7 @@ int main(int argc, char* argv[]) SAFE_DELETE( INPUTQUEUE ); SAFE_DELETE( INPUTMAPPER ); SAFE_DELETE( INPUTFILTER ); + SAFE_DELETE( PROFILEMAN ); // PROFILEMAN needs the songs still loaded SAFE_DELETE( SONGMAN ); SAFE_DELETE( BANNERCACHE ); SAFE_DELETE( SONGINDEX ); @@ -951,7 +952,6 @@ int main(int argc, char* argv[]) SAFE_DELETE( NOTESKIN ); SAFE_DELETE( THEME ); SAFE_DELETE( ANNOUNCER ); - SAFE_DELETE( PROFILEMAN ); SAFE_DELETE( BOOKKEEPER ); SAFE_DELETE( LIGHTSMAN ); SAFE_DELETE( SOUND ); diff --git a/stepmania/src/Steps.cpp b/stepmania/src/Steps.cpp index af44e8471a..0d9fb25a70 100644 --- a/stepmania/src/Steps.cpp +++ b/stepmania/src/Steps.cpp @@ -313,6 +313,19 @@ void SortNotesArrayByDifficulty( vector &arraySteps ) stable_sort( arraySteps.begin(), arraySteps.end(), CompareNotesPointersByDifficulty ); } +bool CompareStepsPointersByTypeAndDifficulty(const Steps *pStep1, const Steps *pStep2) +{ + if( pStep1->m_StepsType < pStep2->m_StepsType ) + return true; + if( pStep1->m_StepsType > pStep2->m_StepsType ) + return false; + return pStep1->GetDifficulty() < pStep2->GetDifficulty(); +} + +void SortStepsByTypeAndDifficulty( vector &arraySongPointers ) +{ + sort( arraySongPointers.begin(), arraySongPointers.end(), CompareStepsPointersByTypeAndDifficulty ); +} bool Steps::MemCardData::HighScore::operator>=( const Steps::MemCardData::HighScore& other ) const diff --git a/stepmania/src/Steps.h b/stepmania/src/Steps.h index c065668a26..1746f2df21 100644 --- a/stepmania/src/Steps.h +++ b/stepmania/src/Steps.h @@ -135,5 +135,7 @@ bool CompareNotesPointersByRadarValues(const Steps* pNotes1, const Steps* pNotes bool CompareNotesPointersByMeter(const Steps *pNotes1, const Steps* pNotes2); bool CompareNotesPointersByDifficulty(const Steps *pNotes1, const Steps *pNotes2); void SortNotesArrayByDifficulty( vector &arrayNotess ); +bool CompareStepsPointersByTypeAndDifficulty(const Steps *pStep1, const Steps *pStep2); +void SortStepsByTypeAndDifficulty( vector &arraySongPointers ); #endif