#include "global.h" /* ----------------------------------------------------------------------------- Class: ProfileHtml Desc: See header. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ #include "ProfileHtml.h" #include "ThemeManager.h" #include "RageFile.h" #include "RageLog.h" #include "SongManager.h" #include "song.h" #include "Steps.h" #include #include "GameManager.h" #include "Course.h" #include "Bookkeeper.h" #include "PrefsManager.h" #define STATS_HTML "stats.html" #define STYLE_CSS "style.css" #define TITLE THEME->GetMetric("ProfileHtml","Title") #define FOOTER THEME->GetMetric("ProfileHtml","Footer") #define VERIFICATION_TEXT THEME->GetMetric("ProfileHtml","VerificationText") static int g_Level = 1; inline CString MakeUniqueId() { CString s="id"+ssprintf("%d%d%d",rand(),rand(),rand()); return s; } inline void PRINT_OPEN(RageFile &f,CString sName,bool bExpanded,CString sID){ g_Level++; ASSERT(g_Level>0 && g_Level<6); f.Write( ssprintf("
\n" "%s\n" "
\n", g_Level, g_Level, sID.c_str(), sName.c_str(), g_Level, sID.c_str(), bExpanded?"visibletext":"hiddentext") ); } inline void PRINT_OPEN(RageFile &f,CString sName,bool bExpanded=false) { PRINT_OPEN(f,sName,bExpanded,MakeUniqueId()); } inline void PRINT_CLOSE(RageFile &f) { f.Write( "
\n" "
\n" ); g_Level--; ASSERT(g_Level>=0); } struct Table { Table() {} struct Line { Line() {} Line(CString n) { sName = n; } Line(CString n,CString v) { sName = n; sValue = v; } Line(CString n,bool v) { sName = n; sValue = ssprintf("%s",v?"yes":"no"); } Line(CString n,int v) { sName = n; sValue = ssprintf("%d",v); } Line(int r,CString n,int v) { sRank = ssprintf("%d",r); sName = n; sValue = ssprintf("%d",v); } Line(int r,CString n,CString sn,int v) { sRank = ssprintf("%d",r); sName = n; sSubName = sn; sValue = ssprintf("%d",v); } Line(int r,CString n,CString sn,CString ssn,int v) { sRank = ssprintf("%d",r); sName = n; sSubName = sn; sSubSubName = ssn; sValue = ssprintf("%d",v); } CString sRank; CString sName; CString sSubName; CString sSubSubName; CString sValue; }; int iNumCols; vector vLines; }; #define BEGIN_TABLE(cols) { Table table; table.iNumCols=cols; #define TABLE_LINE1(p1) table.vLines.push_back( Table::Line(p1) ); #define TABLE_LINE2(p1,p2) table.vLines.push_back( Table::Line(p1,p2) ); #define TABLE_LINE3(p1,p2,p3) table.vLines.push_back( Table::Line(p1,p2,p3) ); #define TABLE_LINE4(p1,p2,p3,p4) table.vLines.push_back( Table::Line(p1,p2,p3,p4) ); #define TABLE_LINE5(p1,p2,p3,p4,p5) table.vLines.push_back( Table::Line(p1,p2,p3,p4,p5) ); #define END_TABLE PrintTable( f, table ); } inline void PrintTable(RageFile &f,Table &table) { const vector &vLines = table.vLines; int &iNumCols = table.iNumCols; ASSERT( iNumCols > 0 ); if( vLines.empty() ) return; bool bPrintRank = !vLines.empty() && !vLines[0].sRank.empty(); int iMaxItemsPerCol = (vLines.size()+iNumCols-1) / iNumCols; iNumCols = (vLines.size()+iMaxItemsPerCol-1) / iMaxItemsPerCol; // round up f.Write("\n"); for( int col=0; col\n"); int iStartItem = col*iMaxItemsPerCol; f.Write("
\n"); for( int i=iStartItem; i"); const Table::Line& line = (i<(int)vLines.size()) ? vLines[i] : Table::Line(); if( bPrintRank ) { f.Write(""); f.Write(""); } if( bPrintRank ) { f.Write(""); } else { f.Write(""); } if( !line.sValue.empty() ) { f.Write(""); f.Write(""); } f.Write( "\n" ); f.Write(""); } f.Write("
"); f.Write( line.sRank ); f.Write(" "); f.Write("

"); f.Write( line.sName ); f.Write("

"); f.Write("

"); f.Write( line.sSubName ); f.Write("

"); f.Write("
"); f.Write( line.sName ); f.Write(" "); f.Write( line.sValue ); f.Write("
\n"); f.Write("\n"); } f.Write("\n"); } void PrintStatistics( RageFile &f, const Profile *pProfile, CString sTitle, vector &vpSongs, vector &vpAllSteps, vector &vStepsTypesToShow, map mapStepsToSong, vector vpCourses ) { PRINT_OPEN(f,sTitle,true); { PRINT_OPEN(f,"General Info",true); { BEGIN_TABLE(2); TABLE_LINE2( "Name", pProfile->m_sName ); TABLE_LINE2( "LastUsedHighScoreName", pProfile->m_sLastUsedHighScoreName ); TABLE_LINE2( "UsingProfileDefaultModifiers", pProfile->m_bUsingProfileDefaultModifiers ); TABLE_LINE2( "DefaultModifiers", pProfile->m_sDefaultModifiers ); TABLE_LINE2( "TotalPlays", pProfile->m_iTotalPlays ); TABLE_LINE2( "TotalPlaySeconds", pProfile->m_iTotalPlaySeconds ); TABLE_LINE2( "TotalGameplaySeconds", pProfile->m_iTotalGameplaySeconds ); TABLE_LINE2( "CurrentCombo", pProfile->m_iCurrentCombo ); TABLE_LINE2( "CaloriesBurned", pProfile->m_fCaloriesBurned ); TABLE_LINE2( "LastMachinePlayed", pProfile->m_sLastMachinePlayed ); END_TABLE; } PRINT_CLOSE(f); PRINT_OPEN(f,"Num Songs Played by PlayMode"); { BEGIN_TABLE(4); FOREACH_PlayMode( pm ) TABLE_LINE2( PlayModeToString(pm), pProfile->m_iNumSongsPlayedByPlayMode[pm] ); END_TABLE; } PRINT_CLOSE(f); PRINT_OPEN(f,"Num Songs Played by Style"); { BEGIN_TABLE(4); 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 TABLE_LINE2( pStyleDef->m_szName, pProfile->m_iNumSongsPlayedByStyle[i] ); } END_TABLE; } PRINT_CLOSE(f); PRINT_OPEN(f,"Num Songs Played by Difficulty"); { BEGIN_TABLE(4); FOREACH_Difficulty( dc ) TABLE_LINE2( DifficultyToString(dc), pProfile->m_iNumSongsPlayedByDifficulty[dc] ); END_TABLE; } PRINT_CLOSE(f); PRINT_OPEN(f,"Num Songs Played by Meter"); { BEGIN_TABLE(4); for( int i=MIN_METER; i<=MAX_METER; i++ ) TABLE_LINE2( ssprintf("Meter %d",i), pProfile->m_iNumSongsPlayedByMeter[i] ); END_TABLE; } PRINT_CLOSE(f); PRINT_OPEN(f,"Grade Count"); { int iGradeCount[NUM_GRADES]; ZERO( iGradeCount ); for( unsigned i=0; iGetStepsHighScoreList(pSteps); if( hsl.vHighScores.empty() ) continue; // no data, skip this one Grade g = hsl.GetTopScore().grade; ASSERT( g != GRADE_NO_DATA ); ASSERT( g < NUM_GRADES ); ASSERT( g >= 0 ); iGradeCount[g] ++; } BEGIN_TABLE(6); for( int g=0; gm_iNumGradeTiersUsed; g++ ) TABLE_LINE2( GradeToThemedString((Grade)g), iGradeCount[g] ); TABLE_LINE2( GradeToThemedString(GRADE_FAILED), iGradeCount[GRADE_FAILED] ); END_TABLE; } PRINT_CLOSE(f); } PRINT_CLOSE(f); } void PrintPopularity( RageFile &f, const Profile *pProfile, CString sTitle, vector &vpSongs, vector &vpAllSteps, vector &vStepsTypesToShow, map mapStepsToSong, vector vpCourses ) { PRINT_OPEN(f, sTitle ); { SortSongPointerArrayByNumPlays( vpSongs, pProfile, true ); Song* pSongPopularThreshold = vpSongs[ vpSongs.size()*2/3 ]; int iPopularNumPlaysThreshold = pProfile->GetSongNumTimesPlayed(pSongPopularThreshold); // unplayed songs are always considered unpopular if( iPopularNumPlaysThreshold == 0 ) iPopularNumPlaysThreshold = 1; unsigned uMaxToShow = min( vpSongs.size(), (unsigned)100 ); { PRINT_OPEN(f, "Most Popular Songs" ); { BEGIN_TABLE(1); for( unsigned i=0; iGetSongNumTimesPlayed(pSong); if( iNumTimesPlayed == 0 || iNumTimesPlayed < iPopularNumPlaysThreshold ) // not popular break; // done searching TABLE_LINE4(i+1, pSong->GetDisplayMainTitle(), pSong->GetDisplaySubTitle(), iNumTimesPlayed ); } END_TABLE; } PRINT_CLOSE(f); } { SortSongPointerArrayByNumPlays( vpSongs, pProfile, false ); PRINT_OPEN(f, "Least Popular Songs" ); { BEGIN_TABLE(1); for( unsigned i=0; iGetSongNumTimesPlayed(pSong); if( iNumTimesPlayed >= iPopularNumPlaysThreshold ) // not unpopular break; // done searching TABLE_LINE4(i+1, pSong->GetDisplayMainTitle(), pSong->GetDisplaySubTitle(), iNumTimesPlayed ); } END_TABLE; } PRINT_CLOSE(f); } { unsigned uNumToShow = min( vpAllSteps.size(), (unsigned)100 ); SortStepsPointerArrayByNumPlays( vpAllSteps, pProfile, true ); PRINT_OPEN(f, "Most Popular Steps" ); { BEGIN_TABLE(1); for( unsigned i=0; iGetStepsNumTimesPlayed(pSteps)==0 ) continue; // skip Song* pSong = mapStepsToSong[pSteps]; CString s; s += GAMEMAN->NotesTypeToString(pSteps->m_StepsType); s += " "; s += DifficultyToString(pSteps->GetDifficulty()); TABLE_LINE5(i+1, pSong->GetDisplayMainTitle(), pSong->GetDisplaySubTitle(), s, pProfile->GetStepsNumTimesPlayed(pSteps) ); } END_TABLE; } PRINT_CLOSE(f); } { unsigned uNumToShow = min( vpCourses.size(), (unsigned)100 ); SortCoursePointerArrayByNumPlays( vpCourses, pProfile, true ); PRINT_OPEN(f, "Most Popular Courses" ); { BEGIN_TABLE(2); for( unsigned i=0; im_sName, pProfile->GetCourseNumTimesPlayed(pCourse) ); } END_TABLE; } PRINT_CLOSE(f); } } PRINT_CLOSE(f); } typedef void (*FnPrintSong)(RageFile &f, const Profile *pProfile, Song* pSong ); typedef void (*FnPrintGroup)(RageFile &f, const Profile *pProfile, CString sGroup ); typedef void (*FnPrintStepsType)(RageFile &f, const Profile *pProfile, StepsType st ); void PrintSongsInGroup( RageFile &f, const Profile *pProfile, CString sGroup, FnPrintSong pFn ) { PRINT_OPEN(f, sGroup ); { vector vpSongs; SONGMAN->GetSongs( vpSongs, sGroup ); for( unsigned i=0; iGetGroupNames( asGroups ); PRINT_OPEN(f, sTitle ); { for( unsigned g=0; g vStepsTypesToShow, FnPrintStepsType pFn ) { PRINT_OPEN(f, sTitle ); { for( unsigned s=0; sGetSongNumTimesPlayed(pSong); if( iNumTimesPlayed == 0 ) return; // skip vector vpSteps = pSong->GetAllSteps(); PRINT_OPEN(f, pSong->GetFullDisplayTitle() ); { BEGIN_TABLE(2); TABLE_LINE2( "NumTimesPlayed", iNumTimesPlayed ); END_TABLE; // // Print Steps list // for( unsigned j=0; jIsAutogen() ) continue; // skip autogen if( pProfile->GetStepsNumTimesPlayed(pSteps)==0 ) continue; // skip const HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps ); CString s = GAMEMAN->NotesTypeToString(pSteps->m_StepsType) + " - " + DifficultyToString(pSteps->GetDifficulty()); PRINT_OPEN(f, s, true); { BEGIN_TABLE(2); TABLE_LINE2( "NumTimesPlayed", hsl.iNumTimesPlayed ); END_TABLE; BEGIN_TABLE(2); for( unsigned i=0; i &vpSongs, vector &vpAllSteps, vector &vStepsTypesToShow, map mapStepsToSong, vector vpCourses ) { PrintGroups( f, pProfile, sTitle, PrintHighScoresForGroup ); } void PrintDifficultyTableForStepsType( RageFile &f, const Profile *pProfile, StepsType st ) { unsigned i; const vector &vpSongs = SONGMAN->GetAllSongs(); PRINT_OPEN(f, GAMEMAN->NotesTypeToString(st).c_str() ); { f.PutLine( "\n" ); // table header row f.Write( "" ); FOREACH_Difficulty( dc ) { if( dc == DIFFICULTY_EDIT ) continue; // skip f.PutLine( ssprintf("", Capitalize(DifficultyToString(dc).Left(3)).c_str()) ); } f.PutLine( "" ); // table body rows for( i=0; i" ); f.Write( "" ); FOREACH_Difficulty( dc ) { if( dc == DIFFICULTY_EDIT ) continue; // skip Steps* pSteps = pSong->GetStepsByDifficulty( st, dc, false ); if( pSteps ) f.PutLine( ssprintf("",pSteps->GetMeter()) ); else f.PutLine( "" ); } f.Write( "" ); } f.PutLine( "
 %s
" ); f.Write( ssprintf("

%s

", pSong->GetDisplayMainTitle().c_str()) ); f.Write( ssprintf("

%s

", pSong->GetDisplaySubTitle().c_str()) ); f.Write( "

%d

 
\n" ); } PRINT_CLOSE(f); } void PrintDifficultyTable( RageFile &f, const Profile *pProfile, CString sTitle, vector &vpSongs, vector &vpAllSteps, vector &vStepsTypesToShow, map mapStepsToSong, vector vpCourses ) { PrintStepsTypes( f, pProfile, sTitle, vStepsTypesToShow, PrintDifficultyTableForStepsType ); } void PrintInventoryForSong( RageFile &f, const Profile *pProfile, Song* pSong ) { vector vpSteps = pSong->GetAllSteps(); PRINT_OPEN(f, pSong->GetFullDisplayTitle().c_str() ); { BEGIN_TABLE(2); TABLE_LINE2( "Artist", pSong->GetDisplayArtist() ); TABLE_LINE2( "GroupName", pSong->m_sGroupName ); float fMinBPM, fMaxBPM; pSong->GetDisplayBPM( fMinBPM, fMaxBPM ); CString sBPM = (fMinBPM==fMaxBPM) ? ssprintf("%.1f",fMinBPM) : ssprintf("%.1f - %.1f",fMinBPM,fMaxBPM); TABLE_LINE2( "BPM", sBPM ); TABLE_LINE2( "Credit", pSong->m_sCredit ); TABLE_LINE2( "MusicLength", SecondsToTime(pSong->m_fMusicLengthSeconds) ); TABLE_LINE2( "Lyrics", !pSong->m_sLyricsFile.empty() ); END_TABLE; // // Print Steps list // for( unsigned j=0; jIsAutogen() ) continue; // skip autogen CString s = GAMEMAN->NotesTypeToString(pSteps->m_StepsType) + " - " + DifficultyToString(pSteps->GetDifficulty()); PRINT_OPEN(f, s, true); // use poister value as the hash { BEGIN_TABLE(2); TABLE_LINE2( "Description", pSteps->GetDescription() ); TABLE_LINE2( "Meter", pSteps->GetMeter() ); END_TABLE; BEGIN_TABLE(2); FOREACH_RadarCategory( cat ) { CString sCat = RadarCategoryToString(cat); float fVal = pSteps->GetRadarValues()[cat]; CString sVal = ssprintf( "%.2f", fVal ); TABLE_LINE2( sCat, sVal ); } END_TABLE; } PRINT_CLOSE(f); } } PRINT_CLOSE(f); } void PrintInventoryForGroup( RageFile &f, const Profile *pProfile, CString sGroup ) { PrintSongsInGroup( f, pProfile, sGroup, PrintInventoryForSong ); } void PrintInventoryList( RageFile &f, const Profile *pProfile, CString sTitle, vector &vpSongs, vector &vpAllSteps, vector &vStepsTypesToShow, map mapStepsToSong, vector vpCourses ) { PrintGroups( f, pProfile, sTitle, PrintInventoryForGroup ); } void PrintBookkeeping( RageFile &f, const Profile *pProfile, CString sTitle, vector &vpSongs, vector &vpAllSteps, vector &vStepsTypesToShow, map mapStepsToSong, vector vpCourses) { PRINT_OPEN(f, sTitle ); { // GetCoinsLastDays { int coins[NUM_LAST_DAYS]; BOOKKEEPER->GetCoinsLastDays( coins ); PRINT_OPEN(f, ssprintf("Coins for Last %d Days",NUM_LAST_DAYS), true ); { BEGIN_TABLE(4); for( int i=0; iGetCoinsLastWeeks( coins ); PRINT_OPEN(f, ssprintf("Coins for Last %d Weeks",NUM_LAST_WEEKS), true ); { BEGIN_TABLE(4); for( int i=0; iGetCoinsByDayOfWeek( coins ); PRINT_OPEN(f, "Coins by Day of Week", true ); { BEGIN_TABLE(4); for( int i=0; iGetCoinsByHour( coins ); PRINT_OPEN(f, ssprintf("Coins for Last %d Hours",HOURS_PER_DAY), true ); { BEGIN_TABLE(4); for( int i=0; i", sFile.c_str(), sFile.c_str() ); TABLE_LINE1( sImgTag ); } END_TABLE; } PRINT_CLOSE(f); } enum SaveType { SAVE_TYPE_PLAYER, SAVE_TYPE_MACHINE }; void SaveStatsWebPageToDir( CString sDir, SaveType saveType, const Profile *pProfile, const Profile *pProfileMachine ) { 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 ); } } // // Print HTML headers // { f.PutLine( ssprintf("\ \n\ \n\ \n\ %s\n\ \n\ \n\ \n\ ", TITLE.c_str(), STYLE_CSS ) ); } CString sType; switch( saveType ) { case SAVE_TYPE_PLAYER: sType = "Player: "; break; case SAVE_TYPE_MACHINE: sType = "Machine: "; break; } 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

%s %s
%s
\n", TITLE.c_str(), sType.c_str(), sName.c_str(), sTime.c_str() ) ); CString sPlayerName = pProfile->GetDisplayName(); CString sMachineName = pProfileMachine->GetDisplayName(); switch( saveType ) { case SAVE_TYPE_PLAYER: PrintStatistics( f, pProfile, sPlayerName+"'s Statistics", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintPopularity( f, pProfile, sPlayerName+"'s Popularity", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintHighScores( f, pProfile, sPlayerName+"'s High Scores", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintScreenshots( f, pProfile, sPlayerName+"'s Screenshots", sDir ); PrintPopularity( f, pProfileMachine, sMachineName+"'s Popularity", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintHighScores( f, pProfileMachine, sMachineName+"'s High Scores", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); break; case SAVE_TYPE_MACHINE: PrintStatistics( f, pProfile, "Statistics", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintPopularity( f, pProfile, "Popularity", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintHighScores( f, pProfile, "High Scores", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintDifficultyTable( f, pProfile, "Difficulty Table", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintInventoryList( f, pProfile, "Song Information", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); PrintBookkeeping( f, pProfile, "Bookkeeping", vpSongs, vpAllSteps, vStepsTypesToShow, mapStepsToSong, vpCourses ); break; default: ASSERT(0); } f.PutLine( ssprintf("\n", FOOTER.c_str()) ); f.PutLine( "" ); 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." ); } void SavePlayerHtmlToDir( CString sDir, const Profile* pProfilePlayer, const Profile* pProfileMachine ) { SaveStatsWebPageToDir( sDir, SAVE_TYPE_PLAYER, pProfilePlayer, pProfileMachine ); } void SaveMachineHtmlToDir( CString sDir, const Profile* pProfileMachine ) { SaveStatsWebPageToDir( sDir, SAVE_TYPE_MACHINE, pProfileMachine, pProfileMachine ); }