Profile HTML stats overhaul

This commit is contained in:
Chris Danford
2004-02-16 02:57:55 +00:00
parent 3a60b3930a
commit 3906ea166f
7 changed files with 807 additions and 512 deletions
+7 -507
View File
@@ -24,9 +24,10 @@
#include "Course.h"
#include <time.h>
#include "ThemeManager.h"
#include "Bookkeeper.h"
#include "CryptManager.h"
#include "PrefsManager.h"
#include "ProfileHtml.h"
#include "ProfileManager.h"
//
// Old file versions for backward compatibility
@@ -47,14 +48,10 @@ const int SM_390A12_COURSE_SCORES_VERSION = 8;
#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()
{
@@ -989,510 +986,13 @@ void Profile::DeleteCategoryScoresFromDirSM390a12( CString sDir ) const
void Profile::SaveStatsWebPageToDir( CString sDir ) const
{
const 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 ) )
if( m_bIsMachineProfile )
{
LOG->Warn( "Couldn't open file '%s'", fn.c_str() );
return;
SaveMachineHtmlToDir( sDir, this );
// SavePlayerHtmlToDir( sDir, this, PROFILEMAN->GetMachineProfile() ); // remove this when done debugging
}
//
// Gather data
//
vector<Song*> vpSongs = SONGMAN->GetAllSongs();
vector<Steps*> vpAllSteps;
map<Steps*,Song*> mapStepsToSong;
else
{
for( unsigned i=0; i<vpSongs.size(); i++ )
{
Song* pSong = vpSongs[i];
vector<Steps*> vpSteps = pSong->GetAllSteps();
for( unsigned j=0; j<vpSteps.size(); j++ )
{
Steps* pSteps = vpSteps[j];
if( pSteps->IsAutogen() )
continue; // skip
vpAllSteps.push_back( pSteps );
mapStepsToSong[pSteps] = pSong;
}
}
SavePlayerHtmlToDir( sDir, this, PROFILEMAN->GetMachineProfile() );
}
vector<Course*> vpCourses;
SONGMAN->GetAllCourses( vpCourses, false );
//
// Calculate which StepTypes to show
//
vector<StepsType> vStepsTypesToShow;
{
for( StepsType st=(StepsType)0; st<NUM_STEPS_TYPES; st=(StepsType)(st+1) )
{
// don't show if there are no Steps of this StepsType
bool bOneSongHasStepsForThisStepsType = false;
for( unsigned i=0; i<vpSongs.size(); i++ )
{
Song* pSong = vpSongs[i];
vector<Steps*> 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( "<html>" );
f.PutLine( "<head>" );
f.PutLine( "<META HTTP-EQUIV=\"Content-Type\" Content=\"text/html; charset=UTF-8\">" );
f.PutLine( ssprintf("<title>%s</title>", STATS_TITLE.c_str() ) );
f.PutLine( ssprintf("<link rel='stylesheet' type='text/css' href='%s'>",STYLE_CSS) );
f.PutLine( "</head>" );
f.PutLine( "<body>" );
}
#define PRINT_SECTION_START(szName) f.Write( ssprintf("<h2><a name='%s'>"szName"</a> <a href='#top'>(top)</a></h2>\n", szName) )
#define PRINT_SECTION_END f.Write( "\n" )
#define PRINT_DIV_START(szName) f.Write( ssprintf("<div class='section1'>\n" "<h3>%s</h3>\n", szName) )
#define PRINT_DIV_START_ANCHOR(uAnchor,szName) f.Write( ssprintf("<div class='section1'>\n" "<h3><a name='%u'>%s</a></h3>\n", (unsigned)uAnchor, szName) )
#define PRINT_DIV_END f.Write( "</div>\n" )
#define PRINT_DIV2_START(szName) f.Write( ssprintf("<div class='section2'>\n" "<h3>%s</h3>\n", szName) )
#define PRINT_DIV2_START_ANCHOR(uAnchor,szName) f.Write( ssprintf("<div class='section2'>\n" "<h3><a name='%u'>%s</a></h3>\n", (unsigned)uAnchor, szName) )
#define PRINT_DIV2_END f.Write( "</div>\n" )
#define PRINT_LINK(szName,szLink) f.Write( ssprintf("<p><a href='%s'>%s</a></p>\n",szLink,szName) )
#define PRINT_LINE_S(szName,sVal) f.Write( ssprintf("<p>%s = <b>%s</b></p>\n",szName,sVal.c_str()) )
#define PRINT_LINE_B(szName,bVal) f.Write( ssprintf("<p>%s = <b>%s</b></p>\n",szName,(bVal)?"yes":"no") )
#define PRINT_LINE_I(szName,iVal) f.Write( ssprintf("<p>%s = <b>%d</b></p>\n",szName,iVal) )
#define PRINT_LINE_RANK(iRank,sName,iVal) f.Write( ssprintf("<p><b>%d</b> - %s (%d)</p>\n",iRank,sName.c_str(),iVal) )
#define PRINT_LINE_RANK_LINK(iRank,sName,szLink,iVal) f.Write( ssprintf("<p><b>%d</b> - <a href='%s'>%s</a> (%d)</p>\n",iRank,szLink,sName.c_str(),iVal) )
//
// Print table of Contents
//
{
CString sName =
pProfile->m_sLastUsedHighScoreName.empty() ?
pProfile->m_sName :
pProfile->m_sLastUsedHighScoreName;
time_t ltime = time( NULL );
CString sTime = ctime( &ltime );
f.Write( ssprintf("<h1><a name='top'>%s for %s - %s</a></h1>\n",STATS_TITLE.c_str(), sName.c_str(), sTime.c_str()) );
PRINT_DIV_START("Table of Contents");
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;
}
//
// Print 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; i<NUM_PLAY_MODES; i++ )
PRINT_LINE_I( PlayModeToString((PlayMode)i).c_str(), pProfile->m_iNumSongsPlayedByPlayMode[i] );
PRINT_DIV_END;
}
// Num Songs Played by Style
{
PRINT_DIV_START( "Num Songs Played by Style" );
for( int i=0; i<NUM_STYLES; i++ )
{
Style style = (Style)i;
const StyleDef* pStyleDef = GAMEMAN->GetStyleDefForStyle(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; i<NUM_DIFFICULTIES; i++ )
PRINT_LINE_I( DifficultyToString((Difficulty)i).c_str(), pProfile->m_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;
}
//
// Print 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; i<uNumToShow; i++ )
{
Song* pSong = vpSongs[i];
PRINT_LINE_RANK_LINK( i+1, pSong->GetFullDisplayTitle(), 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; i<uNumToShow; i++ )
{
Steps* pSteps = vpAllSteps[i];
Song* pSong = mapStepsToSong[pSteps];
CString s;
s += pSong->GetFullDisplayTitle();
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; i<uNumToShow; i++ )
{
Course* pCourse = vpCourses[i];
PRINT_LINE_RANK_LINK( i+1, pCourse->m_sName, ssprintf("#%u",(unsigned)pCourse).c_str(), pProfile->GetCourseNumTimesPlayed(pCourse) );
}
PRINT_DIV_END;
}
PRINT_SECTION_END;
}
//
// Print High score tables
//
{
SortSongPointerArrayByGroupAndTitle( vpSongs );
PRINT_SECTION_START( "High Scores Table" );
for( unsigned s=0; s<vStepsTypesToShow.size(); s++ )
{
StepsType st = vStepsTypesToShow[s];
unsigned i;
PRINT_DIV_START( GAMEMAN->NotesTypeToString(st).c_str() );
f.PutLine( "<table border='1' cellpadding='2' cellspacing='0'>\n" );
// table header row
f.Write( "<tr><td>&nbsp;</td>" );
for( unsigned k=0; k<NUM_DIFFICULTIES; k++ )
{
Difficulty d = (Difficulty)k;
f.PutLine( ssprintf("<td>%s</td>", Capitalize(DifficultyToString(d).Left(3)).c_str()) );
}
f.PutLine( "</tr>" );
// table body rows
for( i=0; i<vpSongs.size(); i++ )
{
Song* pSong = vpSongs[i];
f.PutLine( "<tr>" );
f.Write( ssprintf("<td><a href='#%u'>%s</a></td>",
(unsigned)pSong, // use poister value as the hash
pSong->GetFullDisplayTitle().c_str()) );
for( Difficulty dc=(Difficulty)0; dc<NUM_DIFFICULTIES; dc=(Difficulty)(dc+1) )
{
Steps* pSteps = pSong->GetStepsByDifficulty( st, dc, false );
if( pSteps )
{
const HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps );
CString sHighScore;
if( !hsl.vHighScores.empty() )
{
sHighScore += hsl.vHighScores[0].sName;
sHighScore += "<br>";
sHighScore += GradeToString( hsl.vHighScores[0].grade );
sHighScore += "<br>";
sHighScore += ssprintf("%d",hsl.vHighScores[0].iScore);
}
f.PutLine( ssprintf("<td><p align='right'><a href='#%u'>%s</a></p></td>",
(unsigned)pSteps, // use poister value as the hash
sHighScore.c_str()) );
}
else
{
f.PutLine( "<td>&nbsp;</td>" );
}
}
f.Write( "</tr>" );
}
f.PutLine( "</table>\n" );
PRINT_DIV_END;
}
PRINT_SECTION_END;
}
//
// Print Difficulty tables
//
{
SortSongPointerArrayByGroupAndTitle( vpSongs );
PRINT_SECTION_START( "Difficulty Table" );
for( unsigned s=0; s<vStepsTypesToShow.size(); s++ )
{
StepsType st = vStepsTypesToShow[s];
unsigned i;
PRINT_DIV_START( GAMEMAN->NotesTypeToString(st).c_str() );
f.PutLine( "<table border='1' cellpadding='2' cellspacing='0'>\n" );
// table header row
f.Write( "<tr><td>&nbsp;</td>" );
for( unsigned k=0; k<NUM_DIFFICULTIES; k++ )
{
Difficulty d = (Difficulty)k;
f.PutLine( ssprintf("<td>%s</td>", Capitalize(DifficultyToString(d).Left(3)).c_str()) );
}
f.PutLine( "</tr>" );
// table body rows
for( i=0; i<vpSongs.size(); i++ )
{
Song* pSong = vpSongs[i];
f.PutLine( "<tr>" );
f.Write( ssprintf("<td><a href='#%u'>%s</a></td>",
(unsigned)pSong, // use poister value as the hash
pSong->GetFullDisplayTitle().c_str()) );
for( Difficulty dc=(Difficulty)0; dc<NUM_DIFFICULTIES; dc=(Difficulty)(dc+1) )
{
Steps* pSteps = pSong->GetStepsByDifficulty( st, dc, false );
if( pSteps )
{
f.PutLine( ssprintf("<td><p align='right'><a href='#%u'>%d</a></p></td>",
(unsigned)pSteps, // use poister value as the hash
pSteps->GetMeter()) );
}
else
{
f.PutLine( "<td>&nbsp;</td>" );
}
}
f.Write( "</tr>" );
}
f.PutLine( "</table>\n" );
PRINT_DIV_END;
}
PRINT_SECTION_END;
}
//
// Print song list
//
LOG->Trace( "Writing song list ..." );
{
PRINT_SECTION_START( "Song/Steps List" );
for( unsigned i=0; i<vpSongs.size(); i++ )
{
Song* pSong = vpSongs[i];
vector<Steps*> 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;
//
// Print Steps list
//
for( unsigned j=0; j<vpSteps.size(); j++ )
{
Steps* pSteps = vpSteps[j];
if( pSteps->IsAutogen() )
continue; // skip autogen
const 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<hsl.vHighScores.size(); i++ )
{
const HighScore &hs = hsl.vHighScores[i];
CString sName = ssprintf("#%d",i+1);
CString sHSName = hs.sName.empty() ? "????" : hs.sName;
CString sValue = ssprintf("%s, %s, %i, %.2f%%", sHSName.c_str(), GradeToString(hs.grade).c_str(), hs.iScore, hs.fPercentDP*100);
PRINT_LINE_S( sName.c_str(), sValue );
}
f.PutLine( "</div>\n" );
PRINT_DIV2_END;
}
}
PRINT_SECTION_END;
}
//
// Print 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; i<NUM_LAST_DAYS; i++ )
{
CString sDay = (i==0) ? "Today" : ssprintf("%d day(s) ago",i);
PRINT_LINE_I( sDay.c_str(), coins[i] );
}
PRINT_DIV_END;
}
// GetCoinsLastWeeks
{
int coins[NUM_LAST_WEEKS];
BOOKKEEPER->GetCoinsLastWeeks( coins );
PRINT_DIV_START( ssprintf("Coins for Last %d Weeks",NUM_LAST_WEEKS).c_str() );
for( int i=0; i<NUM_LAST_WEEKS; i++ )
{
CString sWeek = (i==0) ? "This week" : ssprintf("%d week(s) ago",i);
PRINT_LINE_I( sWeek.c_str(), coins[i] );
}
PRINT_DIV_END;
}
// GetCoinsByDayOfWeek
{
int coins[DAYS_IN_WEEK];
BOOKKEEPER->GetCoinsByDayOfWeek( coins );
PRINT_DIV_START( "Coins by Day of Week" );
for( int i=0; i<DAYS_IN_WEEK; i++ )
{
CString sDay = DAY_OF_WEEK_TO_NAME[i];
PRINT_LINE_I( sDay.c_str(), coins[i] );
}
PRINT_DIV_END;
}
// GetCoinsByHour
{
int coins[HOURS_PER_DAY];
BOOKKEEPER->GetCoinsByHour( coins );
PRINT_DIV_START( ssprintf("Coins for Last %d Hours",HOURS_PER_DAY).c_str() );
for( int i=0; i<HOURS_PER_DAY; i++ )
{
CString sHour = ssprintf("hour %d",i);
PRINT_LINE_I( sHour.c_str(), coins[i] );
}
PRINT_DIV_END;
}
PRINT_SECTION_END;
}
PRINT_SECTION_START( "End of File" );
PRINT_SECTION_END;
f.PutLine( "</body>" );
f.PutLine( "</html>" );
//
// 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." );
}