From 4f439be73976d276487096e48e4bf376df684dbb Mon Sep 17 00:00:00 2001 From: Chris Danford Date: Tue, 10 Feb 2004 09:42:01 +0000 Subject: [PATCH] split HighScore to separate file move score data from ProfileManager to Profile store score data in XML for easier forward/backward compatibility --- stepmania/src/Course.cpp | 5 + stepmania/src/Course.h | 2 + stepmania/src/GameConstantsAndTypes.cpp | 2 +- stepmania/src/GameConstantsAndTypes.h | 19 +- stepmania/src/GrooveGraph.cpp | 4 +- stepmania/src/HighScore.cpp | 155 +++ stepmania/src/HighScore.h | 63 ++ stepmania/src/IniFile.cpp | 5 +- stepmania/src/IniFile.h | 2 +- stepmania/src/Makefile.am | 2 +- stepmania/src/PlayerNumber.h | 2 + stepmania/src/Profile.cpp | 1321 ++++++++++++++++++++++- stepmania/src/Profile.h | 145 +-- stepmania/src/ProfileManager.cpp | 1217 +-------------------- stepmania/src/ProfileManager.h | 15 +- stepmania/src/ScreenEvaluation.cpp | 6 +- stepmania/src/ScreenRanking.cpp | 2 +- stepmania/src/ScreenSelectCourse.cpp | 4 +- stepmania/src/Song.cpp | 11 +- stepmania/src/StepMania.dsp | 31 +- stepmania/src/Steps.cpp | 8 +- stepmania/src/Steps.h | 2 + stepmania/src/XmlFile.cpp | 21 + stepmania/src/XmlFile.h | 18 +- stepmania/src/song.h | 2 + 25 files changed, 1727 insertions(+), 1337 deletions(-) create mode 100644 stepmania/src/HighScore.cpp create mode 100644 stepmania/src/HighScore.h diff --git a/stepmania/src/Course.cpp b/stepmania/src/Course.cpp index d8154d3a81..af33354df8 100644 --- a/stepmania/src/Course.cpp +++ b/stepmania/src/Course.cpp @@ -1047,7 +1047,12 @@ void SortCoursePointerArrayByMostPlayed( vector &arrayCoursePointers, P Profile* pProfile = PROFILEMAN->GetProfile(slot); if( pProfile == NULL ) return; // nothing to do since we don't have data + SortCoursePointerArrayByMostPlayed( arrayCoursePointers, pProfile ); +} +void SortCoursePointerArrayByMostPlayed( vector &arrayCoursePointers, Profile* pProfile ) +{ + ASSERT( pProfile ); for(unsigned i = 0; i < arrayCoursePointers.size(); ++i) course_sort_val[arrayCoursePointers[i]] = (float) pProfile->GetCourseNumTimesPlayed(arrayCoursePointers[i]); stable_sort( arrayCoursePointers.begin(), arrayCoursePointers.end(), CompareCoursePointersBySortValueDescending ); diff --git a/stepmania/src/Course.h b/stepmania/src/Course.h index 5cba8c5ba6..694dc5f643 100644 --- a/stepmania/src/Course.h +++ b/stepmania/src/Course.h @@ -20,6 +20,7 @@ struct PlayerOptions; struct SongOptions; class Song; class Steps; +class Profile; enum CourseEntryType { @@ -165,6 +166,7 @@ void SortCoursePointerArrayByAvgDifficulty( vector &apCourses ); void SortCoursePointerArrayByTotalDifficulty( vector &apCourses ); void SortCoursePointerArrayByRanking( vector &apCourses ); void SortCoursePointerArrayByMostPlayed( vector &arrayCoursePointers, ProfileSlot slot ); +void SortCoursePointerArrayByMostPlayed( vector &arrayCoursePointers, Profile* pProfile ); void MoveRandomToEnd( vector &apCourses ); diff --git a/stepmania/src/GameConstantsAndTypes.cpp b/stepmania/src/GameConstantsAndTypes.cpp index b7c2894dd7..a530570bbd 100644 --- a/stepmania/src/GameConstantsAndTypes.cpp +++ b/stepmania/src/GameConstantsAndTypes.cpp @@ -14,7 +14,7 @@ #include "GameState.h" -CString CategoryToString( RadarCategory cat ) +CString RadarCategoryToString( RadarCategory cat ) { switch( cat ) { diff --git a/stepmania/src/GameConstantsAndTypes.h b/stepmania/src/GameConstantsAndTypes.h index 65fc2e7f9d..691beb29e8 100644 --- a/stepmania/src/GameConstantsAndTypes.h +++ b/stepmania/src/GameConstantsAndTypes.h @@ -34,6 +34,10 @@ #define ARROW_SIZE (64) + +#define FOREACH_ENUM( e, max, var ) for( e var=(e)0; varGetMetricF("GrooveGraph","EdgeWidth") #define DIFFICULTY_COLORS(dc) THEME->GetMetricC("GrooveGraph",Capitalize(DifficultyToString(dc))+"Color") -#define SHOW_CATEGORY(cat) THEME->GetMetricB("GrooveGraph","Show"+Capitalize(CategoryToString(cat))) -#define CATEGORY_X(cat) THEME->GetMetricF("GrooveGraph",Capitalize(CategoryToString(cat))+"X") +#define SHOW_CATEGORY(cat) THEME->GetMetricB("GrooveGraph","Show"+Capitalize(RadarCategoryToString(cat))) +#define CATEGORY_X(cat) THEME->GetMetricF("GrooveGraph",Capitalize(RadarCategoryToString(cat))+"X") #define MOUNTAINS_BASE_Y THEME->GetMetricF("GrooveGraph","MountainsBaseY") CachedThemeMetricF MOUNTAIN_WIDTH ("GrooveGraph","MountainWidth"); CachedThemeMetricF MOUNTAIN_HEIGHT ("GrooveGraph","MountainHeight"); diff --git a/stepmania/src/HighScore.cpp b/stepmania/src/HighScore.cpp new file mode 100644 index 0000000000..9cbc612d69 --- /dev/null +++ b/stepmania/src/HighScore.cpp @@ -0,0 +1,155 @@ +#include "global.h" +/* +----------------------------------------------------------------------------- + Class: HighScore + + Desc: See header. + + Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. + Chris Danford +----------------------------------------------------------------------------- +*/ + +#include "HighScore.h" +#include "PrefsManager.h" +#include "GameConstantsAndTypes.h" + +bool HighScore::operator>=( const HighScore& other ) const +{ + /* Make sure we treat AAAA as higher than AAA, even though the score + * is the same. + * + * XXX: Isn't it possible to beat the grade but not beat the score, since + * grading and scores are on completely different systems? Should we be + * checking for these completely separately? */ + if( PREFSMAN->m_bPercentageScoring ) + { + if( fPercentDP == other.fPercentDP ) + return grade >= other.grade; + else + return fPercentDP >= other.fPercentDP; + } + else + { + if( iScore == other.iScore ) + return grade >= other.grade; + else + return iScore >= other.iScore; + } +} + +XNode* HighScore::CreateNode() const +{ + XNode* pNode = new XNode; + pNode->name = "HighScore"; + + if( IsRankingToFillIn(sName) ) + { + // TRICKY: Don't write "name to fill in" markers. + pNode->AppendChild( "Name", IsRankingToFillIn(sName) ? "" : sName ); + } + pNode->AppendChild( "Grade", grade ); + pNode->AppendChild( "Score", iScore ); + pNode->AppendChild( "PercentDP", fPercentDP ); + pNode->AppendChild( "SurviveSeconds", fSurviveSeconds ); + pNode->AppendChild( "Modifiers", sModifiers ); + + return pNode; +} + +void HighScore::LoadFromNode( XNode* pNode ) +{ + ASSERT( pNode->name == "HighScore" ); + XNodes childs = pNode->GetChilds(); + for( int i = 0 ; i < childs.size(); i++) + { + XNode* pChild = childs[i]; + if( pChild->name == "Name" ) pChild->GetValue( sName ); + else if( pChild->name == "Grade" ) pChild->GetValue( (int&)grade ); + else if( pChild->name == "Score" ) pChild->GetValue( iScore ); + else if( pChild->name == "PercentDP" ) pChild->GetValue( fPercentDP ); + else if( pChild->name == "SurviveSeconds" ) pChild->GetValue( fSurviveSeconds ); + else if( pChild->name == "Modifiers" ) pChild->GetValue( sModifiers ); + } +} + + +void HighScoreList::Init() +{ + iNumTimesPlayed = 0; + vHighScores.clear(); +} + +void HighScoreList::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( vHighScores.size() > unsigned(NUM_RANKING_LINES) ) + vHighScores.erase( vHighScores.begin()+NUM_RANKING_LINES, vHighScores.end() ); + } +} + +const HighScore& HighScoreList::GetTopScore() const +{ + if( vHighScores.empty() ) + { + static HighScore hs; + hs = HighScore(); + return hs; + } + else + { + return vHighScores[0]; + } +} + +XNode* HighScoreList::CreateNode() const +{ + XNode* pNode = new XNode; + pNode->name = "HighScoreList"; + + pNode->AppendChild( "NumTimesPlayed", iNumTimesPlayed ); + + for( int i=0; iAppendChild( hs.CreateNode() ); + } + + return pNode; +} + +void HighScoreList::LoadFromNode( XNode* pNode ) +{ + Init(); + + ASSERT( pNode->name == "HighScoreList" ); + for( XNodes::iterator child = pNode->childs.begin(); + child != pNode->childs.end(); + child++) + { + if( (*child)->name == "NumTimesPlayed" ) + { + (*child)->GetValue( iNumTimesPlayed ); + } + else if( (*child)->name == "HighScore" ) + { + vHighScores.resize( vHighScores.size()+1 ); + vHighScores.back().LoadFromNode( (*child) ); + + // ignore all high scores that are 0 + if( vHighScores.back().iScore == 0 ) + vHighScores.pop_back(); + } + } +} + + diff --git a/stepmania/src/HighScore.h b/stepmania/src/HighScore.h new file mode 100644 index 0000000000..862e7cf23c --- /dev/null +++ b/stepmania/src/HighScore.h @@ -0,0 +1,63 @@ +#ifndef HighScore_H +#define HighScore_H +/* +----------------------------------------------------------------------------- + Class: HighScore + + Desc: Player data that persists between sessions. Can be stored on a local + disk or on a memory card. + + Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. + Chris Danford +----------------------------------------------------------------------------- +*/ + +#include "Grade.h" +#include "XmlFile.h" + +struct HighScore +{ + CString sName; + Grade grade; + int iScore; + float fPercentDP; + float fSurviveSeconds; + CString sModifiers; + + HighScore() + { + grade = GRADE_NO_DATA; + iScore = 0; + fPercentDP = 0; + fSurviveSeconds = 0; + } + + bool operator>=( const HighScore& other ) const; + + XNode* CreateNode() const; + void LoadFromNode( XNode* pNode ); +}; + +struct HighScoreList +{ + int iNumTimesPlayed; + vector vHighScores; + + + HighScoreList() + { + iNumTimesPlayed = 0; + } + + void Init(); + + void AddHighScore( HighScore hs, int &iIndexOut ); + + const HighScore& GetTopScore() const; + + XNode* CreateNode() const; + void LoadFromNode( XNode* pNode ); +}; + + +#endif diff --git a/stepmania/src/IniFile.cpp b/stepmania/src/IniFile.cpp index 984fb04d1f..7744419010 100644 --- a/stepmania/src/IniFile.cpp +++ b/stepmania/src/IniFile.cpp @@ -85,14 +85,14 @@ bool IniFile::ReadFile() } // writes data stored in class to ini file -void IniFile::WriteFile() +bool IniFile::WriteFile() { RageFile f; if( !f.Open( path, RageFile::WRITE ) ) { LOG->Trace( "Writing '%s' failed: %s", path.c_str(), f.GetError().c_str() ); error = f.GetError(); - return; + return false; } for (keymap::const_iterator k = keys.begin(); k != keys.end(); ++k) @@ -107,6 +107,7 @@ void IniFile::WriteFile() f.PutLine( "" ); } + return true; } // deletes all stored ini data diff --git a/stepmania/src/IniFile.h b/stepmania/src/IniFile.h index afa789d368..7b9649c3d2 100644 --- a/stepmania/src/IniFile.h +++ b/stepmania/src/IniFile.h @@ -56,7 +56,7 @@ public: bool ReadFile(); //writes data stored in class to ini file - void WriteFile(); + bool WriteFile(); //deletes all stored ini data void Reset(); diff --git a/stepmania/src/Makefile.am b/stepmania/src/Makefile.am index f058a58b09..a62c8cf9fb 100644 --- a/stepmania/src/Makefile.am +++ b/stepmania/src/Makefile.am @@ -57,7 +57,7 @@ DataStructures = \ Attack.h Attack.cpp BannerCache.cpp BannerCache.h Character.cpp Character.h CharacterHead.cpp CharacterHead.h CodeDetector.cpp CodeDetector.h \ Course.cpp Course.h Font.cpp Font.h FontCharAliases.cpp FontCharAliases.h FontCharmaps.cpp FontCharmaps.h Game.h \ GameConstantsAndTypes.cpp GameConstantsAndTypes.h GameDef.cpp GameDef.h GameInput.cpp GameInput.h \ -Grade.cpp Grade.h Inventory.cpp Inventory.h LyricsLoader.cpp LyricsLoader.h MenuInput.h ModeChoice.cpp ModeChoice.h \ +Grade.cpp Grade.h HighScore.cpp HighScore.h Inventory.cpp Inventory.h LyricsLoader.cpp LyricsLoader.h MenuInput.h ModeChoice.cpp ModeChoice.h \ NoteData.cpp NoteData.h NoteDataUtil.cpp NoteDataUtil.h NoteDataWithScoring.cpp NoteDataWithScoring.h \ NoteFieldPositioning.cpp NoteFieldPositioning.h NoteTypes.cpp NoteTypes.h NotesLoader.cpp NotesLoader.h \ NotesLoaderBMS.cpp NotesLoaderBMS.h NotesLoaderDWI.cpp NotesLoaderDWI.h NotesLoaderKSF.cpp NotesLoaderKSF.h \ diff --git a/stepmania/src/PlayerNumber.h b/stepmania/src/PlayerNumber.h index b473f46086..fc334ed26d 100644 --- a/stepmania/src/PlayerNumber.h +++ b/stepmania/src/PlayerNumber.h @@ -15,6 +15,7 @@ #include "RageTypes.h" // for RageColor +#define FOREACH_ENUM( e, max, var ) for( e var=(e)0; var +#include "ThemeManager.h" +#include "Bookkeeper.h" -bool HighScore::operator>=( const HighScore& other ) const -{ - /* Make sure we treat AAAA as higher than AAA, even though the score - * is the same. - * - * XXX: Isn't it possible to beat the grade but not beat the score, since - * grading and scores are on completely different systems? Should we be - * checking for these completely separately? */ - if( PREFSMAN->m_bPercentageScoring ) - { - if( fPercentDP == other.fPercentDP ) - return grade >= other.grade; - else - return fPercentDP >= other.fPercentDP; - } - else - { - if( iScore == other.iScore ) - return grade >= other.grade; - else - return iScore >= other.iScore; - } -} +// +// Old file versions for backward compatibility +// +#define SM_300_STATISTICS_INI "statistics.ini" -void HighScoreList::AddHighScore( HighScore hs, int &iIndexOut ) +#define SM_390A12_CATEGORY_SCORES_DAT "CategoryScores.dat" +#define SM_390A12_SONG_SCORES_DAT "SongScores.dat" +#define SM_390A12_COURSE_SCORES_DAT "CourseScores.dat" +const int SM_390A12_CATEGORY_RANKING_VERSION = 6; +const int SM_390A12_SONG_SCORES_VERSION = 9; +const int SM_390A12_COURSE_SCORES_VERSION = 8; + +// +// Current file versions +// +#define PROFILE_INI "Profile.ini" +#define CATEGORY_SCORES_XML "CategoryScores.xml" +#define SONG_SCORES_XML "SongScores.xml" +#define COURSE_SCORES_XML "CourseScores.xml" +#define STATS_HTML "stats.html" +#define STYLE_CSS "style.css" + + +#define DEFAULT_PROFILE_NAME "" + +#define STATS_TITLE THEME->GetMetric("ProfileManager","StatsTitle") + + +void Profile::InitGeneralData() { + // Fill in a default value in case ini doesn't have it. + m_sName = DEFAULT_PROFILE_NAME; + m_sLastUsedHighScoreName = ""; + m_bUsingProfileDefaultModifiers = false; + m_sDefaultModifiers = ""; + m_iTotalPlays = 0; + m_iTotalPlaySeconds = 0; + m_iTotalGameplaySeconds = 0; + m_iCurrentCombo = 0; + m_fWeightPounds = 0; + m_fCaloriesBurned = 0; + 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( vHighScores.size() > unsigned(NUM_RANKING_LINES) ) - vHighScores.erase( vHighScores.begin()+NUM_RANKING_LINES, vHighScores.end() ); - } + for( i=0; i vpSteps; + pSong->GetSteps( vpSteps ); + for( unsigned i=0; iGetStepsHighScoreList(pSteps).iNumTimesPlayed; + } + return iTotalNumTimesPlayed; +} // // Steps high scores @@ -194,10 +240,10 @@ HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory return m_CategoryHighScores[st][rc]; } -int Profile::GetCategoryNumTimesPlayed( RankingCategory rc ) const +int Profile::GetCategoryNumTimesPlayed( StepsType st ) const { int iNumTimesPlayed = 0; - for( int st=0; stWarn("Error parsing file '%s' at %s:%d",fn.c_str(),__FILE__,__LINE__); +#define WARN_AND_RETURN { WARN; return; } +#define WARN_AND_CONTINUE { WARN; continue; } + +bool Profile::LoadGeneralDataFromDir( CString sDir ) +{ + CString sIniPath = sDir + PROFILE_INI; + InitGeneralData(); + + CStringArray asBits; + split( Dirname(sIniPath), "/", asBits, true ); + CString sLastDir = asBits.back(); // this is a number name, e.g. "0000001" + + // + // read ini + // + IniFile ini( sIniPath ); + if( !ini.ReadFile() ) + return false; + + ini.GetValue( "Profile", "DisplayName", m_sName ); + ini.GetValue( "Profile", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); + ini.GetValue( "Profile", "UsingProfileDefaultModifiers", m_bUsingProfileDefaultModifiers ); + ini.GetValue( "Profile", "DefaultModifiers", m_sDefaultModifiers ); + ini.GetValue( "Profile", "TotalPlays", m_iTotalPlays ); + ini.GetValue( "Profile", "TotalPlaySeconds", m_iTotalPlaySeconds ); + ini.GetValue( "Profile", "TotalGameplaySeconds", m_iTotalGameplaySeconds ); + ini.GetValue( "Profile", "CurrentCombo", m_iCurrentCombo ); + + unsigned i; + for( i=0; iGetGameDefForGame(GAMEMAN->GetStyleDefForStyle((Style)i)->m_Game)->m_szName)+Capitalize(GAMEMAN->GetStyleDefForStyle((Style)i)->m_szName), m_iNumSongsPlayedByStyle[i] ); + for( i=0; iGetGameDefForGame(GAMEMAN->GetStyleDefForStyle((Style)i)->m_Game)->m_szName)+Capitalize(GAMEMAN->GetStyleDefForStyle((Style)i)->m_szName), m_iNumSongsPlayedByStyle[i] ); + for( i=0; i &vpSongs = SONGMAN->GetAllSongs(); + + for( unsigned s=0; sGetSongNumTimesPlayed(pSong) == 0 ) + continue; + + LPXNode pSongNode = xml.AppendChild( "Song" ); + pSongNode->AppendChild( "SongDir", pSong->GetSongDir() ); + + const vector vSteps = pSong->GetAllSteps(); + + for( unsigned n=0; nGetStepsHighScoreList(pSteps).iNumTimesPlayed == 0 ) + continue; + + LPXNode pStepsNode = pSongNode->AppendChild( "Steps" ); + + HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps ); + + pStepsNode->AppendChild( "StepsType", pSteps->m_StepsType ); + pStepsNode->AppendChild( "Difficulty", pSteps->GetDifficulty() ); + pStepsNode->AppendChild( "Description", pSteps->GetDescription() ); + + pStepsNode->AppendChild( hsl.CreateNode() ); + } + } + + xml.SaveToFile( fn ); +} + +void Profile::LoadSongScoresFromDir( CString sDir ) +{ + CHECKPOINT; + + Profile* pProfile = this; + + CString fn = sDir + SONG_SCORES_XML; + + XNode xml; + if( !xml.LoadFromFile( fn ) ) + { + LOG->Warn( "Couldn't open file \"%s\" for writing.", fn.c_str() ); + return; + } + + if( xml.name != "SongScores" ) + WARN_AND_RETURN; + + for( XNodes::iterator song = xml.childs.begin(); + song != xml.childs.end(); + song++ ) + { + if( (*song)->name != "Song" ) + continue; + + CString sSongDir; + if( !(*song)->GetChildValue("SongDir", sSongDir) ) + WARN_AND_CONTINUE; + + Song* pSong = SONGMAN->GetSongFromDir( sSongDir ); + if( pSong == NULL ) + WARN_AND_CONTINUE; + + for( XNodes::iterator steps = (*song)->childs.begin(); + steps != (*song)->childs.end(); + steps++ ) + { + if( (*steps)->name != "Steps" ) + continue; + + StepsType st; + if( !(*steps)->GetChildValue("StepsType", (int&)st) ) + WARN_AND_CONTINUE; + + Difficulty dc; + if( !(*steps)->GetChildValue("Difficulty", (int&)dc) ) + WARN_AND_CONTINUE; + + CString sDescription; + if( !(*steps)->GetChildValue("Description", sDescription) ) + WARN_AND_CONTINUE; + + // Even if pSong or pSteps is null, we still have to skip over that data. + + Steps* pSteps = NULL; + if( dc == DIFFICULTY_EDIT ) + pSteps = pSong->GetStepsByDescription( st, sDescription ); + else + pSteps = pSong->GetStepsByDifficulty( st, dc ); + if( pSteps == NULL ) + WARN_AND_CONTINUE; + + XNode *pHighScoreListNode = (*steps)->GetChild("HighScoreList"); + if( pHighScoreListNode == NULL ) + WARN_AND_CONTINUE; + + HighScoreList &hsl = this->GetStepsHighScoreList( pSteps ); + hsl.LoadFromNode( pHighScoreListNode ); + } + } +} + +void Profile::LoadSongScoresFromDirSM390a12( CString sDir ) +{ + Profile* pProfile = this; + ASSERT( pProfile ); + + CString fn = sDir + SM_390A12_SONG_SCORES_DAT; + + RageFile f; + if( !f.Open(fn, RageFile::READ) ) + { + LOG->Warn( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); + return; + } + + int version; + if( !FileRead(f, version) ) + WARN_AND_RETURN; + if( version != SM_390A12_SONG_SCORES_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( st, sDescription ); + else + pSteps = pSong->GetStepsByDifficulty( st, dc ); + } + + int iNumTimesPlayed; + if( !FileRead(f, iNumTimesPlayed) ) + WARN_AND_RETURN; + + HighScoreList &hsl = pProfile->GetStepsHighScoreList(pSteps); + + if( pSteps ) + hsl.iNumTimesPlayed = iNumTimesPlayed; + + int iNumHighScores; + if( !FileRead(f, iNumHighScores) ) + WARN_AND_RETURN; + + if( pSteps ) + hsl.vHighScores.resize( iNumHighScores ); + + int l; + for( l=0; lWarn( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); + return; + } + + int version; + if( !FileRead(f, version) ) + WARN_AND_RETURN; + if( version != SM_390A12_CATEGORY_RANKING_VERSION ) + WARN_AND_RETURN; + + for( int st=0; stGetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); + hsl.vHighScores.resize( iNumHighScores ); + + for( int l=0; lWarn( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); + return; + } + + int version; + if( !FileRead(f, version) ) + WARN_AND_RETURN; + if( version != SM_390A12_COURSE_SCORES_VERSION ) + WARN_AND_RETURN; + + int iNumCourses; + if( !FileRead(f, iNumCourses) ) + WARN_AND_RETURN; + + for( int c=0; cGetCourseFromPath( 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 NumStepsTypesPlayed = 0; + if( !FileRead(f, NumStepsTypesPlayed) ) + WARN_AND_RETURN; + + while( NumStepsTypesPlayed-- ) + { + int st; + if( !FileRead(f, st) ) + WARN_AND_RETURN; + + int iNumTimesPlayed; + if( !FileRead(f, iNumTimesPlayed) ) + WARN_AND_RETURN; + + HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, (StepsType)st ); + + if( pCourse ) + hsl.iNumTimesPlayed = iNumTimesPlayed; + + int iNumHighScores; + if( !FileRead(f, iNumHighScores) ) + WARN_AND_RETURN; + + if( pCourse ) + hsl.vHighScores.resize(iNumHighScores); + + for( int l=0; l vpCourses; + SONGMAN->GetAllCourses( vpCourses, true ); + + for( unsigned c=0; cGetCourseNumTimesPlayed(pCourse) == 0 ) + continue; + + XNode* pCourseNode = xml.AppendChild( "Course", pCourse->m_bIsAutogen ? pCourse->m_sName : pCourse->m_sPath ); + + for( StepsType st=(StepsType)0; stGetCourseHighScoreList(pCourse, st).iNumTimesPlayed == 0 ) + continue; + + LPXNode pStepsTypeNode = pCourseNode->AppendChild( "StepsType", st ); + + HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, st ); + + pStepsTypeNode->AppendChild( hsl.CreateNode() ); + } + } + + xml.SaveToFile( fn ); +} + +void Profile::LoadCourseScoresFromDir( CString sDir ) +{ + CHECKPOINT; + + Profile* pProfile = this; + + CString fn = sDir + COURSE_SCORES_XML; + + XNode xml; + if( !xml.LoadFromFile( fn ) ) + { + LOG->Warn( "Couldn't open file \"%s\" for writing.", fn.c_str() ); + return; + } + + if( xml.name != "CourseScores" ) + WARN_AND_RETURN; + + for( XNodes::iterator course = xml.childs.begin(); + course != xml.childs.end(); + course++ ) + { + if( (*course)->name != "Course" ) + continue; + + CString sCourse; + (*course)->GetValue(sCourse); + Course* pCourse = SONGMAN->GetCourseFromPath( sCourse ); + if( pCourse == NULL ) + pCourse = SONGMAN->GetCourseFromName( sCourse ); + + for( XNodes::iterator stepsType = (*course)->childs.begin(); + stepsType != (*course)->childs.end(); + stepsType++ ) + { + if( (*stepsType)->name != "StepsType" ) + continue; + + StepsType st; + (*stepsType)->GetValue((int&)st); + + XNode *pHighScoreListNode = (*stepsType)->GetChild("HighScoreList"); + if( pHighScoreListNode == NULL ) + WARN_AND_CONTINUE; + + HighScoreList &hsl = this->GetCourseHighScoreList( pCourse, st ); + hsl.LoadFromNode( pHighScoreListNode ); + } + } + + xml.SaveToFile( fn ); +} + +void Profile::SaveCategoryScoresToDir( CString sDir ) +{ + CHECKPOINT; + + Profile* pProfile = this; + ASSERT( pProfile ); + + CString fn = sDir + CATEGORY_SCORES_XML; + + XNode xml; + xml.name = "CategoryScores"; + + FOREACH_StepsType( st ) + { + // skip steps types that have never been played + if( pProfile->GetCategoryNumTimesPlayed( st ) == 0 ) + continue; + + XNode* pStepsTypeNode = xml.AppendChild( "StepsType", st ); + + FOREACH_RankingCategory( rc ) + { + // skip steps types/categories that have never been played + if( pProfile->GetCategoryHighScoreList(st,rc).iNumTimesPlayed == 0 ) + continue; + + XNode* pRankingCategoryNode = pStepsTypeNode->AppendChild( "RankingCategory", rc ); + + HighScoreList &hsl = pProfile->GetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); + + pRankingCategoryNode->AppendChild( hsl.CreateNode() ); + } + } + + xml.SaveToFile( fn ); +} + +void Profile::LoadCategoryScoresFromDir( CString sDir ) +{ + CHECKPOINT; + + Profile* pProfile = this; + + CString fn = sDir + CATEGORY_SCORES_XML; + + XNode xml; + if( !xml.LoadFromFile( fn ) ) + { + LOG->Warn( "Couldn't open file \"%s\" for writing.", fn.c_str() ); + return; + } + + if( xml.name != "CategoryScores" ) + WARN_AND_RETURN; + + for( XNodes::iterator stepsType = xml.childs.begin(); + stepsType != xml.childs.end(); + stepsType++ ) + { + if( (*stepsType)->name != "StepsType" ) + continue; + + StepsType st; + (*stepsType)->GetValue((int&)st); + + for( XNodes::iterator radarCategory = (*stepsType)->childs.begin(); + radarCategory != (*stepsType)->childs.end(); + radarCategory++ ) + { + if( (*radarCategory)->name != "RankingCategory" ) + continue; + + RankingCategory rc; + (*radarCategory)->GetValue( (int&)rc ); + + XNode *pHighScoreListNode = (*radarCategory)->GetChild("HighScoreList"); + if( pHighScoreListNode == NULL ) + WARN_AND_CONTINUE; + + HighScoreList &hsl = this->GetCategoryHighScoreList( st, rc ); + hsl.LoadFromNode( pHighScoreListNode ); + } + } +} + +void Profile::DeleteSongScoresFromDirSM390a12( CString sDir ) +{ + CString fn = sDir + SM_390A12_SONG_SCORES_DAT; + // FIXME +} + +void Profile::DeleteCourseScoresFromDirSM390a12( CString sDir ) +{ + CString fn = sDir + SM_390A12_COURSE_SCORES_DAT; + // FIXME +} + +void Profile::DeleteCategoryScoresFromDirSM390a12( CString sDir ) +{ + CString fn = sDir + SM_390A12_CATEGORY_SCORES_DAT; + // FIXME +} + +void Profile::SaveStatsWebPageToDir( CString sDir ) +{ + Profile* pProfile = this; + + CString fn = sDir + STATS_HTML; + + LOG->Trace( "Writing %s ...", fn.c_str() ); + + // + // Open file + // + RageFile f; + if( !f.Open( fn, RageFile::WRITE ) ) + { + LOG->Warn( "Couldn't open file '%s'", fn.c_str() ); + return; + } + + // + // Gather data + // + vector vpSongs = SONGMAN->GetAllSongs(); + vector vpAllSteps; + map mapStepsToSong; + { + for( unsigned i=0; i vpSteps = pSong->GetAllSteps(); + for( unsigned j=0; jIsAutogen() ) + continue; // skip + vpAllSteps.push_back( pSteps ); + mapStepsToSong[pSteps] = pSong; + } + } + } + vector vpCourses; + SONGMAN->GetAllCourses( vpCourses, false ); + + // + // Calculate which StepTypes to show + // + vector vStepsTypesToShow; + { + for( StepsType st=(StepsType)0; st vpSteps; + pSong->GetSteps( vpSteps, st, DIFFICULTY_INVALID, -1, -1, "", false ); + if( !vpSteps.empty() ) + { + bOneSongHasStepsForThisStepsType = true; + break; + } + } + + if( bOneSongHasStepsForThisStepsType ) + vStepsTypesToShow.push_back( st ); + } + } + + // + // prist HTML headers + // + { + f.PutLine( "" ); + f.PutLine( "" ); + f.PutLine( "" ); + f.PutLine( ssprintf("%s", STATS_TITLE.c_str() ) ); + f.PutLine( ssprintf("",STYLE_CSS) ); + f.PutLine( "" ); + f.PutLine( "" ); + } + +#define PRINT_SECTION_START(szName) f.Write( ssprintf("

"szName" (top)

\n", szName) ) +#define PRINT_SECTION_END f.Write( "\n" ) +#define PRINT_DIV_START(szName) f.Write( ssprintf("
\n" "

%s

\n", szName) ) +#define PRINT_DIV_START_ANCHOR(uAnchor,szName) f.Write( ssprintf("
\n" "

%s

\n", (unsigned)uAnchor, szName) ) +#define PRINT_DIV_END f.Write( "
\n" ) +#define PRINT_DIV2_START(szName) f.Write( ssprintf("
\n" "

%s

\n", szName) ) +#define PRINT_DIV2_START_ANCHOR(uAnchor,szName) f.Write( ssprintf("
\n" "

%s

\n", (unsigned)uAnchor, szName) ) +#define PRINT_DIV2_END f.Write( "
\n" ) +#define PRINT_LINK(szName,szLink) f.Write( ssprintf("

%s

\n",szLink,szName) ) +#define PRINT_LINE_S(szName,sVal) f.Write( ssprintf("

%s = %s

\n",szName,sVal.c_str()) ) +#define PRINT_LINE_B(szName,bVal) f.Write( ssprintf("

%s = %s

\n",szName,(bVal)?"yes":"no") ) +#define PRINT_LINE_I(szName,iVal) f.Write( ssprintf("

%s = %d

\n",szName,iVal) ) +#define PRINT_LINE_RANK(iRank,sName,iVal) f.Write( ssprintf("

%d - %s (%d)

\n",iRank,sName.c_str(),iVal) ) +#define PRINT_LINE_RANK_LINK(iRank,sName,szLink,iVal) f.Write( ssprintf("

%d - %s (%d)

\n",iRank,szLink,sName.c_str(),iVal) ) + + // + // Prist table of costests + // + { + CString sName = + pProfile->m_sLastUsedHighScoreName.empty() ? + pProfile->m_sName : + pProfile->m_sLastUsedHighScoreName; + time_t ltime = time( NULL ); + CString sTime = ctime( <ime ); + + f.Write( ssprintf("

%s for %s - %s

\n",STATS_TITLE.c_str(), sName.c_str(), sTime.c_str()) ); + PRINT_DIV_START("Table of Costests"); + PRINT_LINK( "Statistics", "#Statistics" ); + PRINT_LINK( "Popularity Lists", "#Popularity Lists" ); + PRINT_LINK( "Difficulty Table", "#Difficulty Table" ); + PRINT_LINK( "High Scores Table", "#High Scores Table" ); + PRINT_LINK( "Song/Steps List", "#Song/Steps List" ); + PRINT_LINK( "Bookkeeping", "#Bookkeeping" ); + PRINT_DIV_END; + } + + // + // Prist Statistics + // + LOG->Trace( "Writing stats ..." ); + { + PRINT_SECTION_START( "Statistics" ); + + // Memory card stats + { + PRINT_DIV_START( "This Profile" ); + PRINT_LINE_S( "Name", pProfile->m_sName ); + PRINT_LINE_S( "LastUsedHighScoreName", pProfile->m_sLastUsedHighScoreName ); + PRINT_LINE_B( "UsingProfileDefaultModifiers", pProfile->m_bUsingProfileDefaultModifiers ); + PRINT_LINE_S( "DefaultModifiers", pProfile->m_sDefaultModifiers ); + PRINT_LINE_I( "TotalPlays", pProfile->m_iTotalPlays ); + PRINT_LINE_I( "TotalPlaySeconds", pProfile->m_iTotalPlaySeconds ); + PRINT_LINE_I( "TotalGameplaySeconds", pProfile->m_iTotalGameplaySeconds ); + PRINT_LINE_I( "CurrentCombo", pProfile->m_iCurrentCombo ); + PRINT_DIV_END; + } + + // Num Songs Played by PlayMode + { + PRINT_DIV_START( "Num Songs Played by PlayMode" ); + for( int i=0; im_iNumSongsPlayedByPlayMode[i] ); + PRINT_DIV_END; + } + + // Num Songs Played by Style + { + PRINT_DIV_START( "Num Songs Played by Style" ); + for( int i=0; iGetStyleDefForStyle(style); + StepsType st = pStyleDef->m_StepsType; + if( !pStyleDef->m_bUsedForGameplay ) + continue; // skip + // only show if this style plays a StepsType that we're showing + if( find(vStepsTypesToShow.begin(),vStepsTypesToShow.end(),st) == vStepsTypesToShow.end() ) + continue; // skip + PRINT_LINE_I( pStyleDef->m_szName, pProfile->m_iNumSongsPlayedByStyle[i] ); + } + PRINT_DIV_END; + } + + // Num Songs Played by Difficulty + { + PRINT_DIV_START( "Num Songs Played by Difficulty" ); + for( int i=0; im_iNumSongsPlayedByDifficulty[i] ); + PRINT_DIV_END; + } + + // Num Songs Played by Meter + { + PRINT_DIV_START( "Num Songs Played by Meter" ); + for( int i=MAX_METER; i>=MIN_METER; i-- ) + PRINT_LINE_I( ssprintf("%d",i).c_str(), pProfile->m_iNumSongsPlayedByMeter[i] ); + PRINT_DIV_END; + } + + PRINT_SECTION_END; + } + + // + // Prist Popularity Lists + // + { + PRINT_SECTION_START( "Popularity Lists" ); + + // Songs by popularity + { + unsigned uNumToShow = min( vpSongs.size(), (unsigned)100 ); + + SortSongPointerArrayByMostPlayed( vpSongs, this ); + PRINT_DIV_START( "Songs by Popularity" ); + for( unsigned i=0; iGetFullDisplayTitle(), ssprintf("#%u",(unsigned)pSong).c_str(), this->GetSongNumTimesPlayed(pSong) ); + } + PRINT_DIV_END; + } + + // Steps by popularity + { + unsigned uNumToShow = min( vpAllSteps.size(), (unsigned)100 ); + + SortStepsPointerArrayByMostPlayed( vpAllSteps, this ); + PRINT_DIV_START( "Steps by Popularity" ); + for( unsigned i=0; iGetFullDisplayTitle(); + s += " - "; + s += GAMEMAN->NotesTypeToString(pSteps->m_StepsType); + s += " "; + s += DifficultyToString(pSteps->GetDifficulty()); + PRINT_LINE_RANK_LINK( i+1, s, ssprintf("#%u",(unsigned)pSteps).c_str(), pProfile->GetStepsNumTimesPlayed(pSteps) ); + } + PRINT_DIV_END; + } + + // Course by popularity + { + unsigned uNumToShow = min( vpCourses.size(), (unsigned)100 ); + + SortCoursePointerArrayByMostPlayed( vpCourses, this ); + PRINT_DIV_START( "Courses by Popularity" ); + for( unsigned i=0; im_sName, ssprintf("#%u",(unsigned)pCourse).c_str(), pProfile->GetCourseNumTimesPlayed(pCourse) ); + } + PRINT_DIV_END; + } + + PRINT_SECTION_END; + } + + // + // Prist High score tables + // + { + SortSongPointerArrayByGroupAndTitle( vpSongs ); + + PRINT_SECTION_START( "High Scores Table" ); + for( unsigned s=0; sNotesTypeToString(st).c_str() ); + f.PutLine( "\n" ); + + // table header row + f.Write( "" ); + for( unsigned k=0; k%s", Capitalize(DifficultyToString(d).Left(3)).c_str()) ); + } + f.PutLine( "" ); + + // table body rows + for( i=0; i" ); + + f.Write( ssprintf("", + (unsigned)pSong, // use poister value as the hash + pSong->GetFullDisplayTitle().c_str()) ); + + for( Difficulty dc=(Difficulty)0; dcGetStepsByDifficulty( st, dc, false ); + if( pSteps ) + { + HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps ); + CString sHighScore; + if( !hsl.vHighScores.empty() ) + { + sHighScore += hsl.vHighScores[0].sName; + sHighScore += "
"; + sHighScore += GradeToString( hsl.vHighScores[0].grade ); + sHighScore += "
"; + sHighScore += ssprintf("%d",hsl.vHighScores[0].iScore); + } + f.PutLine( ssprintf("
", + (unsigned)pSteps, // use poister value as the hash + sHighScore.c_str()) ); + } + else + { + f.PutLine( "" ); + } + } + + f.Write( "" ); + } + + f.PutLine( "
 
%s

%s

 
\n" ); + PRINT_DIV_END; + } + PRINT_SECTION_END; + } + + // + // Prist Difficulty tables + // + { + SortSongPointerArrayByGroupAndTitle( vpSongs ); + + PRINT_SECTION_START( "Difficulty Table" ); + for( unsigned s=0; sNotesTypeToString(st).c_str() ); + f.PutLine( "\n" ); + + // table header row + f.Write( "" ); + for( unsigned k=0; k%s", Capitalize(DifficultyToString(d).Left(3)).c_str()) ); + } + f.PutLine( "" ); + + // table body rows + for( i=0; i" ); + + f.Write( ssprintf("", + (unsigned)pSong, // use poister value as the hash + pSong->GetFullDisplayTitle().c_str()) ); + + for( Difficulty dc=(Difficulty)0; dcGetStepsByDifficulty( st, dc, false ); + if( pSteps ) + { + f.PutLine( ssprintf("", + (unsigned)pSteps, // use poister value as the hash + pSteps->GetMeter()) ); + } + else + { + f.PutLine( "" ); + } + } + + f.Write( "" ); + } + + f.PutLine( "
 
%s

%d

 
\n" ); + PRINT_DIV_END; + } + PRINT_SECTION_END; + } + + // + // Prist song list + // + LOG->Trace( "Writing song list ..." ); + { + PRINT_SECTION_START( "Song/Steps List" ); + for( unsigned i=0; i vpSteps = pSong->GetAllSteps(); + + /* XXX: We can't call pSong->HasBanner on every song; it'll effectively re-traverse the estire + * 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.) */ + /* Don't prist the song banner anyway since this is going on the memory card. -Chris */ + //CString sImagePath = pSong->HasBanner() ? pSong->GetBannerPath() : (pSong->HasBackground() ? pSong->GetBackgroundPath() : "" ); + PRINT_DIV_START_ANCHOR( /*Song primary key*/pSong, pSong->GetFullDisplayTitle().c_str() ); + PRINT_LINE_S( "Artist", pSong->GetDisplayArtist() ); + PRINT_LINE_S( "GroupName", pSong->m_sGroupName ); + float fMinBPM, fMaxBPM; + pSong->GetDisplayBPM( fMinBPM, fMaxBPM ); + CString sBPM = (fMinBPM==fMaxBPM) ? ssprintf("%.1f",fMinBPM) : ssprintf("%.1f - %.1f",fMinBPM,fMaxBPM); + PRINT_LINE_S( "BPM", sBPM ); + PRINT_LINE_I( "NumTimesPlayed", this->GetSongNumTimesPlayed(pSong) ); + PRINT_LINE_S( "Credit", pSong->m_sCredit ); + PRINT_LINE_S( "MusicLength", SecondsToTime(pSong->m_fMusicLengthSeconds) ); + PRINT_LINE_B( "Lyrics", !pSong->m_sLyricsFile.empty() ); + PRINT_DIV_END; + + // + // Prist Steps list + // + for( unsigned j=0; jIsAutogen() ) + continue; // skip autogen + HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps ); + CString s = + GAMEMAN->NotesTypeToString(pSteps->m_StepsType) + + " - " + + DifficultyToString(pSteps->GetDifficulty()); + PRINT_DIV2_START_ANCHOR( /*Steps primary key*/pSteps, s.c_str() ); // use poister value as the hash + PRINT_LINE_I( "NumTimesPlayed", hsl.iNumTimesPlayed ); + + for( unsigned i=0; i\n" ); + PRINT_DIV2_END; + } + } + PRINT_SECTION_END; + } + + // + // Prist Bookkeeping + // + { + PRINT_SECTION_START( "Bookkeeping" ); + + // GetCoinsLastDays + { + int coins[NUM_LAST_DAYS]; + BOOKKEEPER->GetCoinsLastDays( coins ); + PRINT_DIV_START( ssprintf("Coins for Last %d Days",NUM_LAST_DAYS).c_str() ); + for( int i=0; iGetCoinsLastWeeks( coins ); + PRINT_DIV_START( ssprintf("Coins for Last %d Weeks",NUM_LAST_WEEKS).c_str() ); + for( int i=0; iGetCoinsByDayOfWeek( coins ); + PRINT_DIV_START( "Coins by Day of Week" ); + for( int i=0; iGetCoinsByHour( coins ); + PRINT_DIV_START( ssprintf("Coins for Last %d Hours",HOURS_PER_DAY).c_str() ); + for( int i=0; i" ); + f.PutLine( "" ); + + // + // Copy CSS file from theme. If the copy fails, oh well... + // + CString sStyleFile = THEME->GetPathToO("ProfileManager style.css"); + FileCopy( sStyleFile, sDir+STYLE_CSS ); + LOG->Trace( "Done." ); +} diff --git a/stepmania/src/Profile.h b/stepmania/src/Profile.h index 3cae82222e..420368e715 100644 --- a/stepmania/src/Profile.h +++ b/stepmania/src/Profile.h @@ -16,79 +16,33 @@ #include "Style.h" #include "Grade.h" #include +#include "XmlFile.h" +#include "HighScore.h" +class Song; class Steps; class Course; -struct HighScore +class Profile { - CString sName; - Grade grade; - int iScore; - float fPercentDP; - float fSurviveTime; - - HighScore() +public: + Profile() { - grade = GRADE_NO_DATA; - iScore = 0; - fPercentDP = 0; - fSurviveTime = 0; - } - - bool operator>=( const HighScore& other ) const; -}; - -struct HighScoreList -{ - int iNumTimesPlayed; - vector vHighScores; - - - HighScoreList() - { - iNumTimesPlayed = 0; - } - - void AddHighScore( HighScore hs, int &iIndexOut ); - - const HighScore& GetTopScore() const; -}; - - -struct Profile -{ - Profile() { Init(); } - void Init() - { - m_sName = ""; - m_sLastUsedHighScoreName = ""; - m_bUsingProfileDefaultModifiers = false; - m_sDefaultModifiers = ""; - m_iTotalPlays = 0; - m_iTotalPlaySeconds = 0; - m_iTotalGameplaySeconds = 0; - m_iCurrentCombo = 0; - m_fWeightPounds = 0; - m_fCaloriesBurned = 0; - - int i; - for( i=0; i #include "MemoryCardManager.h" #include "XmlFile.h" @@ -37,26 +35,13 @@ ProfileManager* PROFILEMAN = NULL; // global and accessable from anywhere in our program -#define PROFILE_FILE "Profile.ini" - -#define CATEGORY_SCORES_FILE "CategoryScores.dat" -#define SONG_SCORES_FILE "SongScores.dat" -#define COURSE_SCORES_FILE "CourseScores.dat" -#define STATS_HTML_FILE "stats.html" -#define STYLE_CSS_FILE "style.css" #define NEW_MEM_CARD_NAME "" -#define NEW_PROFILE_NAME "" -#define SM_300_STATISTICS_FILE "statistics.ini" #define USER_PROFILES_DIR "Data/LocalProfiles/" #define MACHINE_PROFILE_DIR "Data/MachineProfile/" -const int CATEGORY_RANKING_VERSION = 6; -const int SONG_SCORES_VERSION = 9; -const int COURSE_SCORES_VERSION = 8; -#define STATS_TITLE THEME->GetMetric("ProfileManager","StatsTitle") static const char *MEM_CARD_DIR[NUM_PLAYERS] = { @@ -91,10 +76,9 @@ void ProfileManager::GetLocalProfileNames( vector &asNamesOut ) for( unsigned i=0; iWarn( "Attempting to load profile from '%s' and does not exist", sProfileDir.c_str() ); @@ -115,10 +99,6 @@ bool ProfileManager::LoadProfile( PlayerNumber pn, CString sProfileDir, bool bIs return false; } - ReadCategoryScoresFromDir( m_sProfileDir[pn], (ProfileSlot)pn ); - ReadSongScoresFromDir( m_sProfileDir[pn], (ProfileSlot)pn ); - ReadCourseScoresFromDir( m_sProfileDir[pn], (ProfileSlot)pn ); - // apply saved default modifiers if any if( m_Profile[pn].m_bUsingProfileDefaultModifiers ) { @@ -135,7 +115,7 @@ bool ProfileManager::CreateProfile( CString sProfileDir, CString sName ) Profile pro; pro.m_sName = sName; - bResult = pro.SaveToIni( sProfileDir + PROFILE_FILE ); + bResult = pro.SaveAllToDir( sProfileDir ); if( !bResult ) return false; @@ -161,7 +141,7 @@ bool ProfileManager::LoadProfileFromMemoryCard( PlayerNumber pn ) { UnloadProfile( pn ); #ifndef _XBOX - // moust card + // moust slot if( MEMCARDMAN->GetCardState(pn) == MEMORY_CARD_STATE_READY ) { FILEMAN->Mount( "dir", MEMCARDMAN->GetOsMountDir(pn), MEM_CARD_DIR[pn] ); @@ -200,39 +180,31 @@ bool ProfileManager::CreateMemoryCardProfile( PlayerNumber pn ) return CreateProfile( sDir, NEW_MEM_CARD_NAME ); } - -bool ProfileManager::LoadFirstAvailableProfile( PlayerNumber pn ) -{ - if( LoadProfileFromMemoryCard(pn) ) - return true; - if( LoadDefaultProfileFromMachine(pn) ) - return true; - - return false; -} +//bool ProfileManager::LoadFirstAvailableProfile( PlayerNumber pn ) +//{ +// if( LoadProfileFromMemoryCard(pn) ) +// return true; +// +// if( LoadDefaultProfileFromMachine(pn) ) +// return true; +// +// return false; +//} bool ProfileManager::SaveProfile( PlayerNumber pn ) { if( m_sProfileDir[pn].empty() ) return false; - m_Profile[pn].SaveToIni( m_sProfileDir[pn]+PROFILE_FILE ); - - SaveCategoryScoresToDir( m_sProfileDir[pn], (ProfileSlot)pn ); - SaveSongScoresToDir( m_sProfileDir[pn], (ProfileSlot)pn ); - SaveSongScoresToDirXml( m_sProfileDir[pn], (ProfileSlot)pn ); - SaveCourseScoresToDir( m_sProfileDir[pn], (ProfileSlot)pn ); - SaveStatsWebPageToDir( m_sProfileDir[pn], (ProfileSlot)pn ); - - return true; + return m_Profile[pn].SaveAllToDir( m_sProfileDir[pn] ); } void ProfileManager::UnloadProfile( PlayerNumber pn ) { m_sProfileDir[pn] = ""; m_bWasLoadedFromMemoryCard[pn] = false; - m_Profile[pn].Init(); + m_Profile[pn].InitAll(); } Profile* ProfileManager::GetProfile( PlayerNumber pn ) @@ -253,71 +225,6 @@ CString ProfileManager::GetPlayerName( PlayerNumber pn ) return names[pn]; } -bool Profile::LoadFromIni( CString sIniPath ) -{ - Init(); - - CStringArray asBits; - split( Dirname(sIniPath), "/", asBits, true ); - CString sLastDir = asBits.back(); // this is a number name, e.g. "0000001" - - // Fill in a default value in case ini doesn't have it. - m_sName = NEW_PROFILE_NAME; - - - // - // read ini - // - IniFile ini( sIniPath ); - if( !ini.ReadFile() ) - return false; - - ini.GetValue( "Profile", "DisplayName", m_sName ); - ini.GetValue( "Profile", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); - ini.GetValue( "Profile", "UsingProfileDefaultModifiers", m_bUsingProfileDefaultModifiers ); - ini.GetValue( "Profile", "DefaultModifiers", m_sDefaultModifiers ); - ini.GetValue( "Profile", "TotalPlays", m_iTotalPlays ); - ini.GetValue( "Profile", "TotalPlaySeconds", m_iTotalPlaySeconds ); - ini.GetValue( "Profile", "TotalGameplaySeconds", m_iTotalGameplaySeconds ); - ini.GetValue( "Profile", "CurrentCombo", m_iCurrentCombo ); - - unsigned i; - for( i=0; iGetGameDefForGame(GAMEMAN->GetStyleDefForStyle((Style)i)->m_Game)->m_szName)+Capitalize(GAMEMAN->GetStyleDefForStyle((Style)i)->m_szName), m_iNumSongsPlayedByStyle[i] ); - for( i=0; iGetGameDefForGame(GAMEMAN->GetStyleDefForStyle((Style)i)->m_Game)->m_szName)+Capitalize(GAMEMAN->GetStyleDefForStyle((Style)i)->m_szName), m_iNumSongsPlayedByStyle[i] ); - for( i=0; iWarn("Error parsing file '%s' at %s:%d",fn.c_str(),__FILE__,__LINE__); return; } -void ProfileManager::ReadSongScoresFromDir( CString sDir, ProfileSlot slot ) -{ - Profile* pProfile = GetProfile( slot ); - ASSERT( pProfile ); - - CString fn = sDir + SONG_SCORES_FILE; - - RageFile f; - if( !f.Open(fn, RageFile::READ) ) - { - LOG->Trace( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); - return; - } - - int version; - if( !FileRead(f, version) ) - WARN_AND_RETURN; - if( version != SONG_SCORES_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( st, sDescription ); - else - pNotes = pSong->GetStepsByDifficulty( st, dc ); - } - - int iNumTimesPlayed; - if( !FileRead(f, iNumTimesPlayed) ) - WARN_AND_RETURN; - - HighScoreList &hsl = pProfile->GetStepsHighScoreList(pNotes); - - if( pNotes ) - hsl.iNumTimesPlayed = iNumTimesPlayed; - - int iNumHighScores; - if( !FileRead(f, iNumHighScores) ) - WARN_AND_RETURN; - - if( pNotes ) - hsl.vHighScores.resize( iNumHighScores ); - - int l; - for( l=0; lTrace( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); - return; - } - - int version; - if( !FileRead(f, version) ) - WARN_AND_RETURN; - if( version != CATEGORY_RANKING_VERSION ) - WARN_AND_RETURN; - - for( int st=0; stGetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); - hsl.vHighScores.resize( iNumHighScores ); - - for( int l=0; lTrace( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); - return; - } - - int version; - if( !FileRead(f, version) ) - WARN_AND_RETURN; - if( version != COURSE_SCORES_VERSION ) - WARN_AND_RETURN; - - int iNumCourses; - if( !FileRead(f, iNumCourses) ) - WARN_AND_RETURN; - - for( int c=0; cGetCourseFromPath( 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 NumStepsTypesPlayed = 0; - if( !FileRead(f, NumStepsTypesPlayed) ) - WARN_AND_RETURN; - - while( NumStepsTypesPlayed-- ) - { - int st; - if( !FileRead(f, st) ) - WARN_AND_RETURN; - - int iNumTimesPlayed; - if( !FileRead(f, iNumTimesPlayed) ) - WARN_AND_RETURN; - - HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, (StepsType)st ); - - if( pCourse ) - hsl.iNumTimesPlayed = iNumTimesPlayed; - - int iNumHighScores; - if( !FileRead(f, iNumHighScores) ) - WARN_AND_RETURN; - - if( pCourse ) - hsl.vHighScores.resize(iNumHighScores); - - for( int l=0; lTrace("SongManager::SaveCategoryRankingsToFile"); - - RageFile f; - if( !f.Open(fn, RageFile::WRITE) ) - { - LOG->Warn( "Couldn't open file \"%s\" for writing: %s", fn.c_str(), f.GetError().c_str() ); - return; - } - - FileWrite( f, CATEGORY_RANKING_VERSION ); - - for( int st=0; stGetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); - - FileWrite( f, hsl.vHighScores.size() ); - - for( unsigned l=0; lTrace("SongManager::SaveCourseScoresToFile"); - - RageFile f; - if( !f.Open(fn, RageFile::WRITE) ) - { - LOG->Warn( "Couldn't open file \"%s\" for writing: %s", fn.c_str(), f.GetError().c_str() ); - return; - } - - FileWrite( f, COURSE_SCORES_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 NumStepsTypesPlayed = 0; - int st; - for( st=0; stGetCourseHighScoreList( pCourse, (StepsType)st ); - if( hsl.iNumTimesPlayed ) - ++NumStepsTypesPlayed; - } - FileWrite( f, NumStepsTypesPlayed ); - - for( st=0; stGetCourseHighScoreList( pCourse, (StepsType)st ); - if( hsl.iNumTimesPlayed == 0 ) - continue; - --NumStepsTypesPlayed; - - FileWrite( f, st ); - FileWrite( f, hsl.iNumTimesPlayed ); - FileWrite( f, hsl.vHighScores.size() ); - for( unsigned l=0; lTrace("SongManager::SaveSongScoresToFile %s", fn.c_str()); - - RageFile f; - if( !f.Open(fn, RageFile::WRITE) ) - { - LOG->Warn( "Couldn't open file \"%s\" for writing: %s", fn.c_str(), f.GetError().c_str() ); - return; - } - - FileWrite( f, SONG_SCORES_VERSION ); - - const vector &vpSongs = SONGMAN->GetAllSongs(); - - FileWrite( f, vpSongs.size() ); - - for( unsigned s=0; s vNotesToWrite; - for( unsigned i=0; im_apNotes.size(); ++i ) - { - Steps* pNotes = pSong->m_apNotes[i]; - HighScoreList &hsl = pProfile->GetStepsHighScoreList( pNotes ); - if( hsl.iNumTimesPlayed == 0 && hsl.vHighScores.empty() ) - continue; - vNotesToWrite.push_back( pNotes ); - } - - FileWrite( f, pSong->GetSongDir() ); - FileWrite( f, vNotesToWrite.size() ); - - for( unsigned n=0; nGetStepsHighScoreList( pNotes ); - - FileWrite( f, pNotes->m_StepsType ); - FileWrite( f, pNotes->GetDifficulty() ); - FileWrite( f, pNotes->GetDescription() ); - FileWrite( f, hsl.iNumTimesPlayed ); - - FileWrite( f, hsl.vHighScores.size() ); - - for( int l=0; l<(int)hsl.vHighScores.size(); l++ ) - { - // tricky: wipe out "name to fill in" markers - if( IsRankingToFillIn(hsl.vHighScores[l].sName) ) - hsl.vHighScores[l].sName = ""; - - FileWrite( f, hsl.vHighScores[l].sName ); - FileWrite( f, hsl.vHighScores[l].grade ); - FileWrite( f, hsl.vHighScores[l].iScore ); - FileWrite( f, hsl.vHighScores[l].fPercentDP ); - } - } - } -} - -void ProfileManager::SaveSongScoresToDirXml( CString sDir, ProfileSlot slot ) -{ - Profile* pProfile = GetProfile( slot ); - ASSERT( pProfile ); - - CString fn = sDir + SONG_SCORES_FILE+".xml"; - - LOG->Trace("SongManager::SaveSongScoresToFile %s", fn.c_str()); - - RageFile f; - if( !f.Open(fn, RageFile::WRITE) ) - { - LOG->Warn( "Couldn't open file \"%s\" for writing: %s", fn.c_str(), f.GetError().c_str() ); - return; - } - - XNode xml; - xml.name = "SongScores"; - - const vector &vpSongs = SONGMAN->GetAllSongs(); - - for( unsigned s=0; s vNotesToWrite; - for( unsigned i=0; im_apNotes.size(); ++i ) - { - Steps* pNotes = pSong->m_apNotes[i]; - HighScoreList &hsl = pProfile->GetStepsHighScoreList( pNotes ); - if( hsl.iNumTimesPlayed == 0 && hsl.vHighScores.empty() ) - continue; - vNotesToWrite.push_back( pNotes ); - } - - if( vNotesToWrite.empty() ) - continue; - - LPXNode pSongNode = xml.AppendChild( "Song" ); - pSongNode->AppendChild( "Dir", pSong->GetSongDir() ); - - for( unsigned n=0; nAppendChild( "Steps" ); - - Steps* pNotes = vNotesToWrite[n]; - ASSERT(pNotes); - - HighScoreList &hsl = pProfile->GetStepsHighScoreList( pNotes ); - - pStepsNode->AppendChild( "StepsType", pNotes->m_StepsType ); - pStepsNode->AppendChild( "Difficulty", pNotes->GetDifficulty() ); - pStepsNode->AppendChild( "Description", pNotes->GetDescription() ); - pStepsNode->AppendChild( "NumTimesPlayed", hsl.iNumTimesPlayed ); - - for( int l=0; l<(int)hsl.vHighScores.size(); l++ ) - { - HighScore &hs = hsl.vHighScores[l]; - - LPXNode pHighScoreNode = pSongNode->AppendChild( "HighScore" ); - - // tricky: wipe out "name to fill in" markers - if( IsRankingToFillIn(hs.sName) ) - hs.sName = ""; - - pHighScoreNode->AppendChild( "Name", hs.sName ); - pHighScoreNode->AppendChild( "Grade", hs.grade ); - pHighScoreNode->AppendChild( "Score", hs.iScore ); - pHighScoreNode->AppendChild( "Percent", hs.fPercentDP ); - } - } - } - - FileWrite( f, xml.GetXML() ); -} - -/* static CString HTMLQuoteDoubleQuotes( CString str ) -{ - str.Replace( "\"", """ ); - return str; -} */ - -void ProfileManager::SaveStatsWebPageToDir( CString sDir, ProfileSlot slot ) -{ - CString fn = sDir + STATS_HTML_FILE; - - LOG->Trace( "Writing %s ...", fn.c_str() ); - // - // Get Profile - // - Profile* pProfile; - if( slot == PROFILE_SLOT_MACHINE ) - pProfile = GetMachineProfile(); - else - pProfile = GetProfile( (PlayerNumber)slot ); - ASSERT(pProfile); - - // - // Open file - // - RageFile f; - if( !f.Open( fn, RageFile::WRITE ) ) - { - LOG->Warn( "Couldn't open file '%s'", fn.c_str() ); - return; - } - - // - // Gather data - // - vector vpSongs = SONGMAN->GetAllSongs(); - vector vpAllSteps; - map mapStepsToSong; - { - for( unsigned i=0; i vpSteps = pSong->GetAllSteps(); - for( unsigned j=0; jIsAutogen() ) - continue; // skip - vpAllSteps.push_back( pSteps ); - mapStepsToSong[pSteps] = pSong; - } - } - } - vector vpCourses; - SONGMAN->GetAllCourses( vpCourses, false ); - - // - // Calculate which StepTypes to show - // - vector vStepsTypesToShow; - { - for( StepsType st=(StepsType)0; st vpSteps; - pSong->GetSteps( vpSteps, st, DIFFICULTY_INVALID, -1, -1, "", false ); - if( !vpSteps.empty() ) - { - bOneSongHasStepsForThisStepsType = true; - break; - } - } - - if( bOneSongHasStepsForThisStepsType ) - vStepsTypesToShow.push_back( st ); - } - } - - // - // prist HTML headers - // - { - f.PutLine( "" ); - f.PutLine( "" ); - f.PutLine( "" ); - f.PutLine( ssprintf("%s", STATS_TITLE.c_str() ) ); - f.PutLine( ssprintf("",STYLE_CSS_FILE) ); - f.PutLine( "" ); - f.PutLine( "" ); - } - -#define PRIst_SECTION_START(szName) f.Write( ssprintf("

"szName" (top)

\n", szName) ) -#define PRIst_SECTION_END f.Write( "\n" ) -#define PRIst_DIV_START(szName) f.Write( ssprintf("
\n" "

%s

\n", szName) ) -#define PRIst_DIV_START_ANCHOR(uAnchor,szName) f.Write( ssprintf("
\n" "

%s

\n", (unsigned)uAnchor, szName) ) -#define PRIst_DIV_END f.Write( "
\n" ) -#define PRIst_DIV2_START(szName) f.Write( ssprintf("
\n" "

%s

\n", szName) ) -#define PRIst_DIV2_START_ANCHOR(uAnchor,szName) f.Write( ssprintf("
\n" "

%s

\n", (unsigned)uAnchor, szName) ) -#define PRIst_DIV2_END f.Write( "
\n" ) -#define PRIst_LINK(szName,szLink) f.Write( ssprintf("

%s

\n",szLink,szName) ) -#define PRIst_LINE_S(szName,sVal) f.Write( ssprintf("

%s = %s

\n",szName,sVal.c_str()) ) -#define PRIst_LINE_B(szName,bVal) f.Write( ssprintf("

%s = %s

\n",szName,(bVal)?"yes":"no") ) -#define PRIst_LINE_I(szName,iVal) f.Write( ssprintf("

%s = %d

\n",szName,iVal) ) -#define PRIst_LINE_RANK(iRank,sName,iVal) f.Write( ssprintf("

%d - %s (%d)

\n",iRank,sName.c_str(),iVal) ) -#define PRIst_LINE_RANK_LINK(iRank,sName,szLink,iVal) f.Write( ssprintf("

%d - %s (%d)

\n",iRank,szLink,sName.c_str(),iVal) ) - - // - // Prist table of costests - // - { - CString sName = - pProfile->m_sLastUsedHighScoreName.empty() ? - pProfile->m_sName : - pProfile->m_sLastUsedHighScoreName; - time_t ltime = time( NULL ); - CString sTime = ctime( <ime ); - - f.Write( ssprintf("

%s for %s - %s

\n",STATS_TITLE.c_str(), sName.c_str(), sTime.c_str()) ); - PRIst_DIV_START("Table of Costests"); - PRIst_LINK( "Statistics", "#Statistics" ); - PRIst_LINK( "Popularity Lists", "#Popularity Lists" ); - PRIst_LINK( "Difficulty Table", "#Difficulty Table" ); - PRIst_LINK( "High Scores Table", "#High Scores Table" ); - PRIst_LINK( "Song/Steps List", "#Song/Steps List" ); - PRIst_LINK( "Bookkeeping", "#Bookkeeping" ); - PRIst_DIV_END; - } - - // - // Prist Statistics - // - LOG->Trace( "Writing stats ..." ); - { - PRIst_SECTION_START( "Statistics" ); - - // Memory card stats - { - PRIst_DIV_START( "This Profile" ); - PRIst_LINE_S( "Name", pProfile->m_sName ); - PRIst_LINE_S( "LastUsedHighScoreName", pProfile->m_sLastUsedHighScoreName ); - PRIst_LINE_B( "UsingProfileDefaultModifiers", pProfile->m_bUsingProfileDefaultModifiers ); - PRIst_LINE_S( "DefaultModifiers", pProfile->m_sDefaultModifiers ); - PRIst_LINE_I( "TotalPlays", pProfile->m_iTotalPlays ); - PRIst_LINE_I( "TotalPlaySeconds", pProfile->m_iTotalPlaySeconds ); - PRIst_LINE_I( "TotalGameplaySeconds", pProfile->m_iTotalGameplaySeconds ); - PRIst_LINE_I( "CurrentCombo", pProfile->m_iCurrentCombo ); - PRIst_DIV_END; - } - - // Num Songs Played by PlayMode - { - PRIst_DIV_START( "Num Songs Played by PlayMode" ); - for( int i=0; im_iNumSongsPlayedByPlayMode[i] ); - PRIst_DIV_END; - } - - // Num Songs Played by Style - { - PRIst_DIV_START( "Num Songs Played by Style" ); - for( int i=0; iGetStyleDefForStyle(style); - StepsType st = pStyleDef->m_StepsType; - if( !pStyleDef->m_bUsedForGameplay ) - continue; // skip - // only show if this style plays a StepsType that we're showing - if( find(vStepsTypesToShow.begin(),vStepsTypesToShow.end(),st) == vStepsTypesToShow.end() ) - continue; // skip - PRIst_LINE_I( pStyleDef->m_szName, pProfile->m_iNumSongsPlayedByStyle[i] ); - } - PRIst_DIV_END; - } - - // Num Songs Played by Difficulty - { - PRIst_DIV_START( "Num Songs Played by Difficulty" ); - for( int i=0; im_iNumSongsPlayedByDifficulty[i] ); - PRIst_DIV_END; - } - - // Num Songs Played by Meter - { - PRIst_DIV_START( "Num Songs Played by Meter" ); - for( int i=MAX_METER; i>=MIN_METER; i-- ) - PRIst_LINE_I( ssprintf("%d",i).c_str(), pProfile->m_iNumSongsPlayedByMeter[i] ); - PRIst_DIV_END; - } - - PRIst_SECTION_END; - } - - // - // Prist Popularity Lists - // - { - PRIst_SECTION_START( "Popularity Lists" ); - - // Songs by popularity - { - unsigned uNumToShow = min( vpSongs.size(), (unsigned)100 ); - - SortSongPointerArrayByMostPlayed( vpSongs, slot ); - PRIst_DIV_START( "Songs by Popularity" ); - for( unsigned i=0; iGetFullDisplayTitle(), ssprintf("#%u",(unsigned)pSong).c_str(), PROFILEMAN->GetSongNumTimesPlayed(pSong,slot) ); - } - PRIst_DIV_END; - } - - // Steps by popularity - { - unsigned uNumToShow = min( vpAllSteps.size(), (unsigned)100 ); - - SortStepsPointerArrayByMostPlayed( vpAllSteps, slot ); - PRIst_DIV_START( "Steps by Popularity" ); - for( unsigned i=0; iGetFullDisplayTitle(); - s += " - "; - s += GAMEMAN->NotesTypeToString(pSteps->m_StepsType); - s += " "; - s += DifficultyToString(pSteps->GetDifficulty()); - PRIst_LINE_RANK_LINK( i+1, s, ssprintf("#%u",(unsigned)pSteps).c_str(), pProfile->GetStepsNumTimesPlayed(pSteps) ); - } - PRIst_DIV_END; - } - - // Course by popularity - { - unsigned uNumToShow = min( vpCourses.size(), (unsigned)100 ); - - SortCoursePointerArrayByMostPlayed( vpCourses, slot ); - PRIst_DIV_START( "Courses by Popularity" ); - for( unsigned i=0; im_sName, ssprintf("#%u",(unsigned)pCourse).c_str(), pProfile->GetCourseNumTimesPlayed(pCourse) ); - } - PRIst_DIV_END; - } - - PRIst_SECTION_END; - } - - // - // Prist High score tables - // - { - SortSongPointerArrayByGroupAndTitle( vpSongs ); - - PRIst_SECTION_START( "High Scores Table" ); - for( unsigned s=0; sNotesTypeToString(st).c_str() ); - f.PutLine( "\n" ); - - // table header row - f.Write( "" ); - for( unsigned k=0; k%s", Capitalize(DifficultyToString(d).Left(3)).c_str()) ); - } - f.PutLine( "" ); - - // table body rows - for( i=0; i" ); - - f.Write( ssprintf("", - (unsigned)pSong, // use poister value as the hash - pSong->GetFullDisplayTitle().c_str()) ); - - for( Difficulty dc=(Difficulty)0; dcGetStepsByDifficulty( st, dc, false ); - if( pSteps ) - { - HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps ); - CString sHighScore; - if( !hsl.vHighScores.empty() ) - { - sHighScore += hsl.vHighScores[0].sName; - sHighScore += "
"; - sHighScore += GradeToString( hsl.vHighScores[0].grade ); - sHighScore += "
"; - sHighScore += ssprintf("%d",hsl.vHighScores[0].iScore); - } - f.PutLine( ssprintf("
", - (unsigned)pSteps, // use poister value as the hash - sHighScore.c_str()) ); - } - else - { - f.PutLine( "" ); - } - } - - f.Write( "" ); - } - - f.PutLine( "
 
%s

%s

 
\n" ); - PRIst_DIV_END; - } - PRIst_SECTION_END; - } - - // - // Prist Difficulty tables - // - { - SortSongPointerArrayByGroupAndTitle( vpSongs ); - - PRIst_SECTION_START( "Difficulty Table" ); - for( unsigned s=0; sNotesTypeToString(st).c_str() ); - f.PutLine( "\n" ); - - // table header row - f.Write( "" ); - for( unsigned k=0; k%s", Capitalize(DifficultyToString(d).Left(3)).c_str()) ); - } - f.PutLine( "" ); - - // table body rows - for( i=0; i" ); - - f.Write( ssprintf("", - (unsigned)pSong, // use poister value as the hash - pSong->GetFullDisplayTitle().c_str()) ); - - for( Difficulty dc=(Difficulty)0; dcGetStepsByDifficulty( st, dc, false ); - if( pSteps ) - { - f.PutLine( ssprintf("", - (unsigned)pSteps, // use poister value as the hash - pSteps->GetMeter()) ); - } - else - { - f.PutLine( "" ); - } - } - - f.Write( "" ); - } - - f.PutLine( "
 
%s

%d

 
\n" ); - PRIst_DIV_END; - } - PRIst_SECTION_END; - } - - // - // Prist song list - // - LOG->Trace( "Writing song list ..." ); - { - PRIst_SECTION_START( "Song/Steps List" ); - for( unsigned i=0; i vpSteps = pSong->GetAllSteps(); - - /* XXX: We can't call pSong->HasBanner on every song; it'll effectively re-traverse the estire - * 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.) */ - /* Don't prist the song banner anyway since this is going on the memory card. -Chris */ - //CString sImagePath = pSong->HasBanner() ? pSong->GetBannerPath() : (pSong->HasBackground() ? pSong->GetBackgroundPath() : "" ); - PRIst_DIV_START_ANCHOR( /*Song primary key*/pSong, pSong->GetFullDisplayTitle().c_str() ); - PRIst_LINE_S( "Artist", pSong->GetDisplayArtist() ); - PRIst_LINE_S( "GroupName", pSong->m_sGroupName ); - float fMinBPM, fMaxBPM; - pSong->GetDisplayBPM( fMinBPM, fMaxBPM ); - CString sBPM = (fMinBPM==fMaxBPM) ? ssprintf("%.1f",fMinBPM) : ssprintf("%.1f - %.1f",fMinBPM,fMaxBPM); - PRIst_LINE_S( "BPM", sBPM ); - PRIst_LINE_I( "NumTimesPlayed", PROFILEMAN->GetSongNumTimesPlayed(pSong,slot) ); - PRIst_LINE_S( "Credit", pSong->m_sCredit ); - PRIst_LINE_S( "MusicLength", SecondsToTime(pSong->m_fMusicLengthSeconds) ); - PRIst_LINE_B( "Lyrics", !pSong->m_sLyricsFile.empty() ); - PRIst_DIV_END; - - // - // Prist Steps list - // - for( unsigned j=0; jIsAutogen() ) - continue; // skip autogen - HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps ); - CString s = - GAMEMAN->NotesTypeToString(pSteps->m_StepsType) + - " - " + - DifficultyToString(pSteps->GetDifficulty()); - PRIst_DIV2_START_ANCHOR( /*Steps primary key*/pSteps, s.c_str() ); // use poister value as the hash - PRIst_LINE_I( "NumTimesPlayed", hsl.iNumTimesPlayed ); - - for( unsigned i=0; i\n" ); - PRIst_DIV2_END; - } - } - PRIst_SECTION_END; - } - - // - // Prist Bookkeeping - // - { - PRIst_SECTION_START( "Bookkeeping" ); - - // GetCoinsLastDays - { - int coins[NUM_LAST_DAYS]; - BOOKKEEPER->GetCoinsLastDays( coins ); - PRIst_DIV_START( ssprintf("Coins for Last %d Days",NUM_LAST_DAYS).c_str() ); - for( int i=0; iGetCoinsLastWeeks( coins ); - PRIst_DIV_START( ssprintf("Coins for Last %d Weeks",NUM_LAST_WEEKS).c_str() ); - for( int i=0; iGetCoinsByDayOfWeek( coins ); - PRIst_DIV_START( "Coins by Day of Week" ); - for( int i=0; iGetCoinsByHour( coins ); - PRIst_DIV_START( ssprintf("Coins for Last %d Hours",HOURS_PER_DAY).c_str() ); - for( int i=0; i" ); - f.PutLine( "" ); - - // - // Copy CSS file from theme. If the copy fails, oh well... - // - CString sStyleFile = THEME->GetPathToO("ProfileManager style.css"); - FileCopy( sStyleFile, sDir+STYLE_CSS_FILE ); - LOG->Trace( "Done." ); - -} - bool ProfileManager::ProfileWasLoadedFromMemoryCard( PlayerNumber pn ) { return GetProfile(pn) && m_bWasLoadedFromMemoryCard[pn]; @@ -1576,18 +420,9 @@ Profile* ProfileManager::GetProfile( ProfileSlot slot ) // // Song stats // -int ProfileManager::GetSongNumTimesPlayed( Song* pSong, ProfileSlot card ) const +int ProfileManager::GetSongNumTimesPlayed( Song* pSong, ProfileSlot slot ) { - int iTotalNumTimesPlayed = 0; - vector vpSteps; - pSong->GetSteps( vpSteps ); - for( unsigned i=0; iGetMachineProfile()->GetStepsHighScoreList(pSteps).iNumTimesPlayed; - } - - return iTotalNumTimesPlayed; + return GetProfile(slot)->GetSongNumTimesPlayed( pSong ); } void ProfileManager::AddStepsHighScore( const Steps* pSteps, PlayerNumber pn, HighScore hs, int &iPersonalIndexOut, int &iMachineIndexOut ) diff --git a/stepmania/src/ProfileManager.h b/stepmania/src/ProfileManager.h index 64000e3cf8..9890e3d2d6 100644 --- a/stepmania/src/ProfileManager.h +++ b/stepmania/src/ProfileManager.h @@ -34,7 +34,7 @@ public: void GetLocalProfileNames( vector &asNamesOut ); bool LoadProfileFromMemoryCard( PlayerNumber pn ); - bool LoadFirstAvailableProfile( PlayerNumber pn ); // memory card or local profile +// bool LoadFirstAvailableProfile( PlayerNumber pn ); // memory card or local profile bool SaveProfile( PlayerNumber pn ); void UnloadProfile( PlayerNumber pn ); @@ -74,8 +74,8 @@ public: // // Song stats // - int GetSongNumTimesPlayed( Song* pSong, ProfileSlot card ) const; - bool IsSongNew( Song* pSong ) const { return GetSongNumTimesPlayed(pSong,PROFILE_SLOT_MACHINE)==0; } + int GetSongNumTimesPlayed( Song* pSong, ProfileSlot card ); + bool IsSongNew( Song* pSong ) { return GetSongNumTimesPlayed(pSong,PROFILE_SLOT_MACHINE)==0; } void AddStepsHighScore( const Steps* pSteps, PlayerNumber pn, HighScore hs, int &iPersonalIndexOut, int &iMachineIndexOut ); void IncrementStepsPlayCount( const Steps* pSteps, PlayerNumber pn ); HighScore GetHighScoreForDifficulty( const Song *s, const StyleDef *st, ProfileSlot slot, Difficulty dc ); @@ -94,15 +94,6 @@ public: // void ReadSM300NoteScores(); - void ReadSongScoresFromDir( CString sDir, ProfileSlot slot ); - void ReadCourseScoresFromDir( CString sDir, ProfileSlot slot ); - void ReadCategoryScoresFromDir( CString sDir, ProfileSlot slot ); - - void SaveSongScoresToDirXml( CString sDir, ProfileSlot slot ); - void SaveSongScoresToDir( CString sDir, ProfileSlot slot ); - void SaveCourseScoresToDir( CString sDir, ProfileSlot slot ); - void SaveCategoryScoresToDir( CString sDir, ProfileSlot slot ); - void SaveStatsWebPageToDir( CString sDir, ProfileSlot slot ); private: bool LoadDefaultProfileFromMachine( PlayerNumber pn ); diff --git a/stepmania/src/ScreenEvaluation.cpp b/stepmania/src/ScreenEvaluation.cpp index de6e5bfd15..72b2a566c8 100644 --- a/stepmania/src/ScreenEvaluation.cpp +++ b/stepmania/src/ScreenEvaluation.cpp @@ -864,7 +864,8 @@ void ScreenEvaluation::CommitScores( const StageStats &stageStats, int iPersonal hs.grade = stageStats.GetGrade( (PlayerNumber)p ); hs.iScore = stageStats.iScore[p]; hs.fPercentDP = stageStats.GetPercentDancePoints( (PlayerNumber)p ); - hs.fSurviveTime = stageStats.fAliveSeconds[p]; + hs.fSurviveSeconds = stageStats.fAliveSeconds[p]; + hs.sModifiers = GAMESTATE->m_PlayerOptions[p].GetString(); StepsType nt = GAMESTATE->GetCurrentStyleDef()->m_StepsType; @@ -894,6 +895,9 @@ void ScreenEvaluation::CommitScores( const StageStats &stageStats, int iPersonal if( hs.fPercentDP > PREFSMAN->m_fMinPercentageForHighScore ) PROFILEMAN->AddCategoryHighScore( nt, rc[p], (PlayerNumber)p, hs, iPersonalHighScoreIndex[p], iMachineHighScoreIndex[p] ); + + // TRICKY: Increment play count here, and not on ScreenGameplay like the others. + PROFILEMAN->IncrementCategoryPlayCount( nt, rc[p], (PlayerNumber)p ); } break; diff --git a/stepmania/src/ScreenRanking.cpp b/stepmania/src/ScreenRanking.cpp index c6300992c1..709e156dd5 100644 --- a/stepmania/src/ScreenRanking.cpp +++ b/stepmania/src/ScreenRanking.cpp @@ -734,7 +734,7 @@ float ScreenRanking::SetPage( PageToShow pts ) if( pts.pCourse->IsOni() ) { m_textPoints[l].SetText( ssprintf("%04d",hs.iScore) ); - m_textTime[l].SetText( SecondsToTime(hs.fSurviveTime) ); + m_textTime[l].SetText( SecondsToTime(hs.fSurviveSeconds) ); m_textScores[l].SetText( "" ); } else { m_textPoints[l].SetText( "" ); diff --git a/stepmania/src/ScreenSelectCourse.cpp b/stepmania/src/ScreenSelectCourse.cpp index aebce0055b..3b10590d31 100644 --- a/stepmania/src/ScreenSelectCourse.cpp +++ b/stepmania/src/ScreenSelectCourse.cpp @@ -432,8 +432,8 @@ void ScreenSelectCourse::AfterCourseChange() if ( pCourse->IsOni() || pCourse->IsEndless() ) { /* use survive time */ - float fSurviveTime = hsl.GetTopScore().fSurviveTime; - CString s = SecondsToTime(fSurviveTime); + float fSurviveSeconds = hsl.GetTopScore().fSurviveSeconds; + CString s = SecondsToTime(fSurviveSeconds); /* dim the inital unsignificant digits */ /*XXX we'd like to have a dimmed ':' and '.', but diff --git a/stepmania/src/Song.cpp b/stepmania/src/Song.cpp index 905b8f4987..ce66a9f381 100644 --- a/stepmania/src/Song.cpp +++ b/stepmania/src/Song.cpp @@ -1369,8 +1369,17 @@ bool CompareSongPointersBySortValueDescending(const Song *pSong1, const Song *pS void SortSongPointerArrayByMostPlayed( vector &arraySongPointers, ProfileSlot slot ) { + Profile* pProfile = PROFILEMAN->GetProfile(slot); + if( pProfile == NULL ) + return; // nothing to do since we don't have data + SortSongPointerArrayByMostPlayed( arraySongPointers, pProfile ); +} + +void SortSongPointerArrayByMostPlayed( vector &arraySongPointers, Profile* pProfile ) +{ + ASSERT( pProfile ); for(unsigned i = 0; i < arraySongPointers.size(); ++i) - song_sort_val[arraySongPointers[i]] = ssprintf("%9i", PROFILEMAN->GetSongNumTimesPlayed(arraySongPointers[i],slot)); + song_sort_val[arraySongPointers[i]] = ssprintf("%9i", pProfile->GetSongNumTimesPlayed(arraySongPointers[i])); stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueDescending ); song_sort_val.clear(); } diff --git a/stepmania/src/StepMania.dsp b/stepmania/src/StepMania.dsp index 647f3600fb..cb287576c8 100644 --- a/stepmania/src/StepMania.dsp +++ b/stepmania/src/StepMania.dsp @@ -65,7 +65,7 @@ IntDir=.\../Debug6 TargetDir=\stepmania\stepmania\Program TargetName=StepMania-debug SOURCE="$(InputPath)" -PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ +PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi # End Special Build Tool @@ -103,8 +103,8 @@ IntDir=.\../Debug_Xbox TargetDir=\stepmania\stepmania TargetName=default SOURCE="$(InputPath)" -PreLink_Cmds=disasm\verinc cl /Zl /nologo /c \ - verstub.cpp /Fo$(IntDir)\ +PreLink_Cmds=disasm\verinc cl /Zl /nologo /c \ + verstub.cpp /Fo$(IntDir)\ PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi ia32.vdi # End Special Build Tool @@ -144,7 +144,7 @@ IntDir=.\../Release6 TargetDir=\stepmania\stepmania\Program TargetName=StepMania SOURCE="$(InputPath)" -PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ +PreLink_Cmds=disasm\verinc cl /Zl /nologo /c verstub.cpp /Fo$(IntDir)\ PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi # End Special Build Tool @@ -184,8 +184,8 @@ IntDir=.\../Release_Xbox TargetDir=\stepmania\stepmania TargetName=default SOURCE="$(InputPath)" -PreLink_Cmds=disasm\verinc cl /Zl /nologo /c \ - verstub.cpp /Fo$(IntDir)\ +PreLink_Cmds=disasm\verinc cl /Zl /nologo /c \ + verstub.cpp /Fo$(IntDir)\ PostBuild_Cmds=disasm\mapconv $(IntDir)\$(TargetName).map $(TargetDir)\StepMania.vdi ia32.vdi # End Special Build Tool @@ -1291,6 +1291,25 @@ SOURCE=.\Grade.h # End Source File # Begin Source File +SOURCE=.\HighScore.cpp + +!IF "$(CFG)" == "StepMania - Win32 Debug" + +!ELSEIF "$(CFG)" == "StepMania - Xbox Debug" + +!ELSEIF "$(CFG)" == "StepMania - Win32 Release" + +!ELSEIF "$(CFG)" == "StepMania - Xbox Release" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\HighScore.h +# End Source File +# Begin Source File + SOURCE=.\Inventory.cpp !IF "$(CFG)" == "StepMania - Win32 Debug" diff --git a/stepmania/src/Steps.cpp b/stepmania/src/Steps.cpp index f7ecc86379..8b81bdb4c6 100644 --- a/stepmania/src/Steps.cpp +++ b/stepmania/src/Steps.cpp @@ -330,8 +330,14 @@ bool CompareStepsPointersBySortValueDescending(const Steps *pSteps1, const Steps void SortStepsPointerArrayByMostPlayed( vector &vStepsPointers, ProfileSlot slot ) { Profile* pProfile = PROFILEMAN->GetProfile(slot); - ASSERT( pProfile ); // we shouldn't be requesting this sort unless there's a Profile in this slot + if( pProfile == NULL ) + return; // nothing to do since we don't have data + SortStepsPointerArrayByMostPlayed( vStepsPointers, pProfile ); +} +void SortStepsPointerArrayByMostPlayed( vector &vStepsPointers, Profile* pProfile ) +{ + ASSERT( pProfile ); for(unsigned i = 0; i < vStepsPointers.size(); ++i) steps_sort_val[vStepsPointers[i]] = ssprintf("%9i", pProfile->GetStepsNumTimesPlayed(vStepsPointers[i])); stable_sort( vStepsPointers.begin(), vStepsPointers.end(), CompareStepsPointersBySortValueDescending ); diff --git a/stepmania/src/Steps.h b/stepmania/src/Steps.h index 7c520e847f..d0c9f1e9d7 100644 --- a/stepmania/src/Steps.h +++ b/stepmania/src/Steps.h @@ -17,6 +17,7 @@ #include "PlayerNumber.h" #include "Grade.h" class NoteData; +class Profile; class Steps { @@ -90,5 +91,6 @@ void SortNotesArrayByDifficulty( vector &arrayNotess ); bool CompareStepsPointersByTypeAndDifficulty(const Steps *pStep1, const Steps *pStep2); void SortStepsByTypeAndDifficulty( vector &arraySongPointers ); void SortStepsPointerArrayByMostPlayed( vector &vStepsPointers, ProfileSlot slot ); +void SortStepsPointerArrayByMostPlayed( vector &vStepsPointers, Profile* pProfile ); #endif diff --git a/stepmania/src/XmlFile.cpp b/stepmania/src/XmlFile.cpp index 824fa4c0ef..10caab0ec7 100644 --- a/stepmania/src/XmlFile.cpp +++ b/stepmania/src/XmlFile.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "RageFile.h" static const TCHAR chXMLTagOpen = '<'; @@ -1185,3 +1186,23 @@ CString XEntity2Ref( LPCTSTR str ) { return entityDefault.Entity2Ref( str ); } + +bool _tagXMLNode::LoadFromFile( CString sFile, LPPARSEINFO pi ) +{ + RageFile f; + if( !f.Open(sFile, RageFile::READ) ) + return false; + CString s; + f.Read( s ); + this->Load( s, pi ); + return true; +} + +bool _tagXMLNode::SaveToFile( CString sFile, LPDISP_OPT opt ) +{ + RageFile f; + if( !f.Open(sFile, RageFile::WRITE) ) + return false; + f.Write( this->GetXML(opt) ); + return true; +} diff --git a/stepmania/src/XmlFile.h b/stepmania/src/XmlFile.h index e1d313c165..4ec119cb74 100644 --- a/stepmania/src/XmlFile.h +++ b/stepmania/src/XmlFile.h @@ -1,8 +1,10 @@ +#ifndef XmlFile_H +#define XmlFile_H // XmlFile.h: interface for the XmlFile class. // -// Adapted from http://www.codeproject.com/cpp/xmlite.asp -// by Chris. The CodeProject FAQ says "all developers may freely -// use the code in their own applications". +// Adapted from http://www.codeproject.com/cpp/xmlite.asp. +// On 2004-02-09 Cho,Kyung Min gave us permission to use and modify this +// library. // // XmlFile : XML Lite Parser Library // by bro ( Cho,Kyung Min: bro@shinbiro.com ) 2002-10-30 @@ -121,6 +123,9 @@ typedef struct _tagXMLNode // name and value CString name; CString value; + void GetValue(CString &out) { out = value; } + void GetValue(int &out) { out = atoi(value); } + void GetValue(float &out) { out = atof(value); } // internal variables LPXNode parent; // parent node @@ -132,6 +137,9 @@ typedef struct _tagXMLNode LPTSTR LoadAttributes( LPCTSTR pszAttrs, LPPARSEINFO pi = &piDefault ); CString GetXML( LPDISP_OPT opt = &optDefault ); + bool LoadFromFile( CString sFile, LPPARSEINFO pi = &piDefault ); + bool SaveToFile( CString sFile, LPDISP_OPT opt = &optDefault ); + // in own attribute list LPXAttr GetAttr( LPCTSTR attrname ); LPCTSTR GetAttrValue( LPCTSTR attrname ); @@ -140,6 +148,9 @@ typedef struct _tagXMLNode // in one level child nodes LPXNode GetChild( LPCTSTR name ); LPCTSTR GetChildValue( LPCTSTR name ); + bool GetChildValue(LPCTSTR name,CString &out) { XNode* pChild=GetChild(name); if(pChild==NULL) return false; pChild->GetValue(out); return true; } + bool GetChildValue(LPCTSTR name,int &out) { XNode* pChild=GetChild(name); if(pChild==NULL) return false; pChild->GetValue(out); return true; } + bool GetChildValue(LPCTSTR name,float &out) { XNode* pChild=GetChild(name); if(pChild==NULL) return false; pChild->GetValue(out); return true; } XNodes GetChilds( LPCTSTR name ); XNodes GetChilds(); @@ -191,3 +202,4 @@ inline bool XIsEmptyString( LPCTSTR str ) return ( s.empty() || s == "" ); } +#endif diff --git a/stepmania/src/song.h b/stepmania/src/song.h index 0f04aa8c49..f4ae526069 100644 --- a/stepmania/src/song.h +++ b/stepmania/src/song.h @@ -20,6 +20,7 @@ class Steps; class StyleDef; class NotesLoader; class LyricsLoader; +class Profile; extern const int FILE_CACHE_VERSION; @@ -210,6 +211,7 @@ void SortSongPointerArrayByArtist( vector &arraySongPointers ); void SortSongPointerArrayByGroupAndDifficulty( vector &arraySongPointers ); void SortSongPointerArrayByGroupAndTitle( vector &arraySongPointers ); void SortSongPointerArrayByMostPlayed( vector &arraySongPointers, ProfileSlot slot ); +void SortSongPointerArrayByMostPlayed( vector &arraySongPointers, Profile* pProfile ); void SortSongPointerArrayByMeter( vector &arraySongPointers, Difficulty dc ); CString GetSectionNameFromSongAndSort( const Song* pSong, SongSortOrder so ); void SortSongPointerArrayBySectionName( vector &arraySongPointers, SongSortOrder so );