Files
itgmania212121/stepmania/src/Profile.cpp
T

1782 lines
48 KiB
C++
Raw Normal View History

2004-02-08 18:25:30 +00:00
#include "global.h"
/*
-----------------------------------------------------------------------------
2004-02-09 06:26:13 +00:00
Class: Profile
2004-02-08 18:25:30 +00:00
Desc: See header.
Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved.
Chris Danford
-----------------------------------------------------------------------------
*/
2004-02-09 06:26:13 +00:00
#include "Profile.h"
2004-02-08 18:25:30 +00:00
#include "RageUtil.h"
2004-02-09 06:26:13 +00:00
#include "PrefsManager.h"
2004-02-10 09:42:01 +00:00
#include "XmlFile.h"
#include "IniFile.h"
#include "GameManager.h"
#include "RageLog.h"
#include "RageFile.h"
#include "song.h"
#include "SongManager.h"
#include "Steps.h"
#include "Course.h"
2004-04-05 05:22:32 +00:00
#include <ctime>
2004-02-10 09:42:01 +00:00
#include "ThemeManager.h"
2004-02-15 04:47:32 +00:00
#include "CryptManager.h"
2004-02-16 02:57:55 +00:00
#include "ProfileHtml.h"
#include "ProfileManager.h"
#include "RageFileManager.h"
2004-03-11 06:31:30 +00:00
#include "ScoreKeeperMAX2.h"
2004-03-14 17:49:38 +00:00
#include "crypto/CryptRand.h"
2004-02-09 06:26:13 +00:00
2004-02-10 09:42:01 +00:00
//
// Old file versions for backward compatibility
//
#define SM_300_STATISTICS_INI "statistics.ini"
#define SM_390A12_CATEGORY_SCORES_DAT "CategoryScores.dat"
#define SM_390A12_SONG_SCORES_DAT "SongScores.dat"
#define SM_390A12_COURSE_SCORES_DAT "CourseScores.dat"
#define SM_390A12_PROFILE_INI "Profile.ini"
2004-02-10 09:42:01 +00:00
const int SM_390A12_CATEGORY_RANKING_VERSION = 6;
const int SM_390A12_SONG_SCORES_VERSION = 9;
const int SM_390A12_COURSE_SCORES_VERSION = 8;
2004-04-18 05:46:43 +00:00
#define GUID_SIZE_BYTES 8
2004-02-10 09:42:01 +00:00
2004-02-22 07:07:03 +00:00
#if defined(WIN32)
#pragma warning (disable : 4706) // assignment within conditional expression
#endif
#define FOREACH_Node( Node, Var ) \
XNodes::const_iterator Var##Iter; \
const XNode *Var = NULL; \
for( Var##Iter = Node->childs.begin(); \
(Var##Iter != Node->childs.end() && (Var = *Var##Iter) ), Var##Iter != Node->childs.end(); \
++Var##Iter )
void Profile::InitEditableData()
{
m_sDisplayName = "";
2004-02-22 02:41:10 +00:00
m_sLastUsedHighScoreName = "";
2004-02-23 04:53:02 +00:00
m_iWeightPounds = 0;
}
2004-02-10 09:42:01 +00:00
void Profile::InitGeneralData()
2004-02-09 06:26:13 +00:00
{
2004-03-14 17:49:38 +00:00
// Init m_iGuid.
// Does the RNG need to be inited and seeded every time?
random_init();
random_add_noise( "ai8049ujr3odusj" );
{
2004-04-18 05:46:43 +00:00
m_sGuid = "";
for( unsigned i=0; i<GUID_SIZE_BYTES; i++ )
m_sGuid += ssprintf( "%hx", random_byte() );
2004-03-14 17:49:38 +00:00
}
2004-02-10 09:42:01 +00:00
m_bUsingProfileDefaultModifiers = false;
m_sDefaultModifiers = "";
2004-03-12 08:31:40 +00:00
m_SortOrder = SORT_INVALID;
2004-03-25 09:54:28 +00:00
m_LastDifficulty = DIFFICULTY_INVALID;
m_LastCourseDifficulty = COURSE_DIFFICULTY_INVALID;
m_pLastSong = NULL;
2004-02-10 09:42:01 +00:00
m_iTotalPlays = 0;
m_iTotalPlaySeconds = 0;
m_iTotalGameplaySeconds = 0;
m_iCurrentCombo = 0;
2004-02-22 20:58:39 +00:00
m_fTotalCaloriesBurned = 0;
m_iTotalDancePoints = 0;
m_iNumExtraStagesPassed = 0;
m_iNumExtraStagesFailed = 0;
m_iNumToasties = 0;
m_UnlockedSongs.clear();
2004-04-18 05:46:43 +00:00
m_sLastPlayedMachineGuid = "";
2004-02-22 19:54:59 +00:00
m_iTotalTapsAndHolds = 0;
m_iTotalJumps = 0;
m_iTotalHolds = 0;
m_iTotalMines = 0;
m_iTotalHands = 0;
2004-02-10 09:42:01 +00:00
int i;
for( i=0; i<NUM_PLAY_MODES; i++ )
m_iNumSongsPlayedByPlayMode[i] = 0;
for( i=0; i<NUM_STYLES; i++ )
m_iNumSongsPlayedByStyle[i] = 0;
for( i=0; i<NUM_DIFFICULTIES; i++ )
m_iNumSongsPlayedByDifficulty[i] = 0;
for( i=0; i<MAX_METER+1; i++ )
m_iNumSongsPlayedByMeter[i] = 0;
ZERO( m_iNumSongsPassedByPlayMode );
ZERO( m_iNumSongsPassedByGrade );
2004-02-09 06:26:13 +00:00
}
2004-02-10 09:42:01 +00:00
void Profile::InitSongScores()
2004-02-09 06:26:13 +00:00
{
m_SongHighScores.clear();
2004-02-09 06:26:13 +00:00
}
2004-02-10 09:42:01 +00:00
void Profile::InitCourseScores()
2004-02-09 06:26:13 +00:00
{
2004-02-10 09:42:01 +00:00
m_CourseHighScores.clear();
}
void Profile::InitCategoryScores()
{
for( int st=0; st<NUM_STEPS_TYPES; st++ )
for( int rc=0; rc<NUM_RANKING_CATEGORIES; rc++ )
m_CategoryHighScores[st][rc].Init();
2004-02-09 06:26:13 +00:00
}
2004-02-08 18:25:30 +00:00
2004-02-17 01:16:57 +00:00
void Profile::InitScreenshotData()
{
m_vScreenshots.clear();
}
2004-02-22 23:29:13 +00:00
void Profile::InitCalorieData()
{
m_mapDayToCaloriesBurned.clear();
}
2004-03-07 04:34:49 +00:00
void Profile::InitAwards()
{
FOREACH_StepsType( st )
{
FOREACH_Difficulty( dc )
FOREACH_PerDifficultyAward( pda )
m_PerDifficultyAwards[st][dc][pda].Unset();
}
FOREACH_PeakComboAward( pca )
{
m_PeakComboAwards[pca].Unset();
}
}
2004-02-16 05:35:06 +00:00
CString Profile::GetDisplayName() const
2004-02-08 18:25:30 +00:00
{
if( !m_sDisplayName.empty() )
return m_sDisplayName;
2004-02-08 18:25:30 +00:00
else if( !m_sLastUsedHighScoreName.empty() )
return m_sLastUsedHighScoreName;
else
2004-02-16 05:35:06 +00:00
return "NoName";
2004-02-08 18:25:30 +00:00
}
2004-02-22 20:58:39 +00:00
CString Profile::GetDisplayTotalCaloriesBurned() const
2004-02-08 18:25:30 +00:00
{
2004-02-23 04:53:02 +00:00
if( m_iWeightPounds == 0 ) // weight not entered
2004-02-08 18:25:30 +00:00
return "N/A";
else
2004-02-23 01:47:59 +00:00
return Commify((int)m_fTotalCaloriesBurned) + " Cal";
2004-02-08 18:25:30 +00:00
}
int Profile::GetTotalNumSongsPlayed() const
2004-02-08 18:25:30 +00:00
{
int iTotal = 0;
for( int i=0; i<NUM_PLAY_MODES; i++ )
iTotal += m_iNumSongsPlayedByPlayMode[i];
return iTotal;
}
int Profile::GetTotalNumSongsPassed() const
{
int iTotal = 0;
for( int i=0; i<NUM_PLAY_MODES; i++ )
iTotal += m_iNumSongsPassedByPlayMode[i];
return iTotal;
}
2004-03-11 06:31:30 +00:00
int Profile::GetTotalHighScoreDancePointsForStepsType( StepsType st ) const
{
int iTotal = 0;
2004-03-12 05:24:32 +00:00
// add steps high scores
2004-03-11 06:31:30 +00:00
{
for( std::map<SongID,HighScoresForASong>::const_iterator i = m_SongHighScores.begin();
i != m_SongHighScores.end();
i++ )
2004-03-11 06:31:30 +00:00
{
SongID id = i->first;
Song* pSong = id.ToSong();
// If the Song isn't loaded on the current machine, then we can't
// get radar values to compute dance points.
if( pSong == NULL )
continue;
const HighScoresForASong &hsfas = i->second;
for( std::map<StepsID,HighScoresForASteps>::const_iterator j = hsfas.m_StepsHighScores.begin();
j != hsfas.m_StepsHighScores.end();
j++ )
2004-03-12 05:24:32 +00:00
{
StepsID id = j->first;
Steps* pSteps = id.ToSteps( pSong, true );
// If the Steps isn't loaded on the current machine, then we can't
// get radar values to compute dance points.
if( pSteps == NULL )
continue;
const HighScoresForASteps& h = j->second;
const HighScoreList& hs = h.hs;
if( pSteps->m_StepsType == st )
{
const RadarValues& fRadars = pSteps->GetRadarValues();
int iPossibleDP = ScoreKeeperMAX2::GetPossibleDancePoints( fRadars );
iTotal += (int)truncf( hs.GetTopScore().fPercentDP * iPossibleDP );
}
2004-03-12 05:24:32 +00:00
}
2004-03-11 06:31:30 +00:00
}
}
2004-03-12 05:24:32 +00:00
// add course high scores
{
for( std::map<const Course*,HighScoresForACourse>::const_iterator iter = m_CourseHighScores.begin();
iter != m_CourseHighScores.end();
iter++ )
{
const Course* pCourse = iter->first;
ASSERT( pCourse );
// Don't count any course that has any entries that change over time.
if( !pCourse->AllSongsAreFixed() )
continue;
2004-03-12 05:24:32 +00:00
const HighScoresForACourse& h = iter->second;
FOREACH_CourseDifficulty( cd )
{
const HighScoreList& hs = h.hs[st][cd];
const RadarValues& fRadars = pCourse->GetRadarValues(st,cd);
2004-03-12 05:24:32 +00:00
int iPossibleDP = ScoreKeeperMAX2::GetPossibleDancePoints( fRadars );
iTotal += (int)truncf( hs.GetTopScore().fPercentDP * iPossibleDP );
}
}
}
2004-03-11 06:31:30 +00:00
return iTotal;
}
2004-02-10 09:42:01 +00:00
CString Profile::GetProfileDisplayNameFromDir( CString sDir )
{
2004-02-22 03:44:04 +00:00
Profile profile;
profile.LoadEditableDataFromDir( sDir );
return profile.GetDisplayName();
2004-02-10 09:42:01 +00:00
}
2004-02-10 10:06:34 +00:00
int Profile::GetSongNumTimesPlayed( const Song* pSong ) const
{
SongID songID;
songID.FromSong( pSong );
return GetSongNumTimesPlayed( songID );
}
int Profile::GetSongNumTimesPlayed( const SongID& songID ) const
2004-02-10 09:42:01 +00:00
{
int iTotalNumTimesPlayed = 0;
std::map<SongID,HighScoresForASong> &songHighScores = ((Profile*)(this))->m_SongHighScores;
const HighScoresForASong& hsSong = songHighScores[songID];
for( std::map<StepsID,HighScoresForASteps>::const_iterator j = hsSong.m_StepsHighScores.begin();
j != hsSong.m_StepsHighScores.end();
j++ )
2004-02-10 09:42:01 +00:00
{
const HighScoresForASteps &hsSteps = j->second;
iTotalNumTimesPlayed += hsSteps.hs.iNumTimesPlayed;
2004-02-10 09:42:01 +00:00
}
return iTotalNumTimesPlayed;
}
2004-02-08 18:25:30 +00:00
2004-02-09 06:26:13 +00:00
//
// Steps high scores
//
void Profile::AddStepsHighScore( const Song* pSong, const Steps* pSteps, HighScore hs, int &iIndexOut )
2004-02-09 06:26:13 +00:00
{
GetStepsHighScoreList(pSong,pSteps).AddHighScore( hs, iIndexOut );
2004-02-09 06:26:13 +00:00
}
const HighScoreList& Profile::GetStepsHighScoreList( const Song* pSong, const Steps* pSteps ) const
2004-02-10 10:06:34 +00:00
{
return ((Profile*)this)->GetStepsHighScoreList(pSong,pSteps);
2004-02-10 10:06:34 +00:00
}
HighScoreList& Profile::GetStepsHighScoreList( const Song* pSong, const Steps* pSteps )
2004-02-09 06:26:13 +00:00
{
SongID songID;
songID.FromSong( pSong );
StepsID stepsID;
stepsID.FromSteps( pSteps );
HighScoresForASong &hsSong = m_SongHighScores[songID]; // operator[] inserts into map
HighScoresForASteps &hsSteps = hsSong.m_StepsHighScores[stepsID]; // operator[] inserts into map
return hsSteps.hs;
2004-02-09 06:26:13 +00:00
}
int Profile::GetStepsNumTimesPlayed( const Song* pSong, const Steps* pSteps ) const
2004-02-09 06:26:13 +00:00
{
return GetStepsHighScoreList(pSong,pSteps).iNumTimesPlayed;
2004-02-09 06:26:13 +00:00
}
void Profile::IncrementStepsPlayCount( const Song* pSong, const Steps* pSteps )
2004-02-09 06:26:13 +00:00
{
GetStepsHighScoreList(pSong,pSteps).iNumTimesPlayed++;
2004-02-09 06:26:13 +00:00
}
//
// Course high scores
//
2004-02-22 06:04:01 +00:00
void Profile::AddCourseHighScore( const Course* pCourse, StepsType st, CourseDifficulty cd, HighScore hs, int &iIndexOut )
2004-02-09 06:26:13 +00:00
{
std::map<const Course*,HighScoresForACourse>::iterator iter = m_CourseHighScores.find( pCourse );
if( iter == m_CourseHighScores.end() )
2004-02-22 06:04:01 +00:00
m_CourseHighScores[pCourse].hs[st][cd].AddHighScore( hs, iIndexOut ); // operator[] inserts into map
2004-02-09 06:26:13 +00:00
else
2004-02-22 06:04:01 +00:00
iter->second.hs[st][cd].AddHighScore( hs, iIndexOut );
2004-02-09 06:26:13 +00:00
}
2004-02-22 06:04:01 +00:00
const HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, StepsType st, CourseDifficulty cd ) const
2004-02-10 10:06:34 +00:00
{
2004-02-22 06:04:01 +00:00
return ((Profile *)this)->m_CourseHighScores[pCourse].hs[st][cd];
2004-02-10 10:06:34 +00:00
}
2004-02-22 06:04:01 +00:00
HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, StepsType st, CourseDifficulty cd )
2004-02-09 06:26:13 +00:00
{
std::map<const Course*,HighScoresForACourse>::iterator iter = m_CourseHighScores.find( pCourse );
if( iter == m_CourseHighScores.end() )
2004-02-22 06:04:01 +00:00
return m_CourseHighScores[pCourse].hs[st][cd]; // operator[] inserts into map
2004-02-09 06:26:13 +00:00
else
2004-02-22 06:04:01 +00:00
return iter->second.hs[st][cd];
2004-02-09 06:26:13 +00:00
}
int Profile::GetCourseNumTimesPlayed( const Course* pCourse ) const
{
std::map<const Course*,HighScoresForACourse>::const_iterator iter = m_CourseHighScores.find( pCourse );
if( iter == m_CourseHighScores.end() )
{
return 0;
}
else
{
int iTotalNumTimesPlayed = 0;
for( unsigned st = 0; st < NUM_STEPS_TYPES; ++st )
2004-02-22 06:04:01 +00:00
FOREACH_CourseDifficulty( cd )
iTotalNumTimesPlayed += iter->second.hs[st][cd].iNumTimesPlayed;
2004-02-09 06:26:13 +00:00
return iTotalNumTimesPlayed;
}
}
2004-02-22 06:04:01 +00:00
void Profile::IncrementCoursePlayCount( const Course* pCourse, StepsType st, CourseDifficulty cd )
2004-02-09 06:26:13 +00:00
{
2004-02-22 06:04:01 +00:00
GetCourseHighScoreList(pCourse,st,cd).iNumTimesPlayed++;
2004-02-09 06:26:13 +00:00
}
//
// Category high scores
//
void Profile::AddCategoryHighScore( StepsType st, RankingCategory rc, HighScore hs, int &iIndexOut )
{
m_CategoryHighScores[st][rc].AddHighScore( hs, iIndexOut );
}
2004-02-10 10:06:34 +00:00
const HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc ) const
{
return ((Profile *)this)->m_CategoryHighScores[st][rc];
}
2004-02-09 06:26:13 +00:00
HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc )
{
return m_CategoryHighScores[st][rc];
}
2004-02-10 09:42:01 +00:00
int Profile::GetCategoryNumTimesPlayed( StepsType st ) const
2004-02-09 06:26:13 +00:00
{
int iNumTimesPlayed = 0;
2004-02-10 09:42:01 +00:00
FOREACH_RankingCategory( rc )
2004-02-09 06:26:13 +00:00
iNumTimesPlayed += m_CategoryHighScores[st][rc].iNumTimesPlayed;
return iNumTimesPlayed;
}
void Profile::IncrementCategoryPlayCount( StepsType st, RankingCategory rc )
{
m_CategoryHighScores[st][rc].iNumTimesPlayed++;
}
2004-02-10 09:42:01 +00:00
//
// Loading and saving
//
#define WARN LOG->Warn("Error parsing file at %s:%d",__FILE__,__LINE__);
#define WARN_AND_RETURN { WARN; return; }
#define WARN_AND_CONTINUE { WARN; continue; }
#define WARN_AND_BREAK { WARN; break; }
#define LOAD_NODE(X) { \
XNode* X = xml.GetChild(#X); \
if( X==NULL ) LOG->Warn("Failed to read section " #X); \
else Load##X##FromNode(X); }
int g_iOnceCtr;
#define FOR_ONCE for(g_iOnceCtr=0;g_iOnceCtr<1;g_iOnceCtr++)
2004-02-15 04:47:32 +00:00
bool Profile::LoadAllFromDir( CString sDir )
{
CHECKPOINT;
2004-02-15 04:47:32 +00:00
InitAll();
// Only try to load old score formats if we're allowing unsigned data.
if( !PREFSMAN->m_bSignProfileData )
{
LoadProfileDataFromDirSM390a12( sDir );
2004-02-15 04:47:32 +00:00
LoadSongScoresFromDirSM390a12( sDir );
LoadCourseScoresFromDirSM390a12( sDir );
LoadCategoryScoresFromDirSM390a12( sDir );
}
2004-02-22 02:41:10 +00:00
LoadEditableDataFromDir( sDir );
2004-02-22 02:41:10 +00:00
// Read stats.xml
FOR_ONCE
{
2004-02-22 02:41:10 +00:00
CString fn = sDir + STATS_XML;
2004-02-20 02:32:16 +00:00
if( !IsAFile(fn) )
break;
2004-02-22 02:41:10 +00:00
LOG->Trace( "Reading profile data '%s'", fn.c_str() );
//
2004-02-22 02:41:10 +00:00
// Don't unreasonably large stats.xml files.
//
2004-02-22 02:41:10 +00:00
int iBytes = FILEMAN->GetFileSizeInBytes( fn );
if( iBytes > REASONABLE_STATS_XML_SIZE_BYTES )
{
LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() );
break;
}
2004-02-22 02:41:10 +00:00
if( PREFSMAN->m_bSignProfileData )
{
2004-02-22 02:41:10 +00:00
CString sStatsXmlSigFile = fn+SIGNATURE_APPEND;
CString sDontShareFile = sDir + DONT_SHARE_SIG;
2004-02-22 02:41:10 +00:00
// verify the stats.xml signature with the "don't share" file
if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) )
{
LOG->Warn( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() );
break;
}
2004-02-22 02:41:10 +00:00
// verify stats.xml
if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) )
{
LOG->Warn( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() );
break;
}
}
XNode xml;
if( !xml.LoadFromFile( fn ) )
{
LOG->Warn( "Couldn't open file '%s' for reading.", fn.c_str() );
break;
}
if( xml.name != "Stats" )
WARN_AND_BREAK;
LOAD_NODE( GeneralData );
LOAD_NODE( SongScores );
LOAD_NODE( CourseScores );
LOAD_NODE( CategoryScores );
LOAD_NODE( ScreenshotData );
2004-02-22 23:29:13 +00:00
LOAD_NODE( CalorieData );
2004-03-07 04:34:49 +00:00
LOAD_NODE( Awards );
}
return true; // FIXME? Investigate what happens if we always return true.
2004-02-15 04:47:32 +00:00
}
bool Profile::SaveAllToDir( CString sDir ) const
{
2004-04-18 05:46:43 +00:00
m_sLastPlayedMachineGuid = PROFILEMAN->GetMachineProfile()->m_sGuid;
2004-02-16 05:35:06 +00:00
// Save editable.xml
2004-02-22 02:41:10 +00:00
SaveEditableDataToDir( sDir );
// Save stats.xml
{
CString fn = sDir + STATS_XML;
XNode xml;
xml.name = "Stats";
xml.AppendChild( SaveGeneralDataCreateNode() );
xml.AppendChild( SaveSongScoresCreateNode() );
xml.AppendChild( SaveCourseScoresCreateNode() );
xml.AppendChild( SaveCategoryScoresCreateNode() );
xml.AppendChild( SaveScreenshotDataCreateNode() );
2004-02-22 23:29:13 +00:00
xml.AppendChild( SaveCalorieDataCreateNode() );
2004-03-07 04:34:49 +00:00
xml.AppendChild( SaveAwardsCreateNode() );
2004-02-22 02:41:10 +00:00
bool bSaved = xml.SaveToFile(fn);
if( bSaved && PREFSMAN->m_bSignProfileData )
{
CString sStatsXmlSigFile = fn+SIGNATURE_APPEND;
CryptManager::SignFileToFile(fn, sStatsXmlSigFile);
// Save the "don't share" file
CString sDontShareFile = sDir + DONT_SHARE_SIG;
CryptManager::SignFileToFile(sStatsXmlSigFile, sDontShareFile);
}
}
2004-02-15 04:47:32 +00:00
// Delete old files after saving new ones so we don't try to load old
// files the next time and make duplicate records.
if( !PREFSMAN->m_bSignProfileData ) // we tried to read the older formats
{
DeleteProfileDataFromDirSM390a12( sDir );
DeleteSongScoresFromDirSM390a12( sDir );
DeleteCourseScoresFromDirSM390a12( sDir );
DeleteCategoryScoresFromDirSM390a12( sDir );
}
2004-02-15 04:47:32 +00:00
SaveStatsWebPageToDir( sDir );
return true;
2004-02-15 04:47:32 +00:00
}
void Profile::LoadProfileDataFromDirSM390a12( CString sDir )
2004-02-10 09:42:01 +00:00
{
CString fn = sDir + SM_390A12_PROFILE_INI;
InitEditableData();
2004-02-10 09:42:01 +00:00
InitGeneralData();
//
// read ini
//
2004-02-15 04:47:32 +00:00
IniFile ini( fn );
2004-02-10 09:42:01 +00:00
if( !ini.ReadFile() )
return;
2004-02-10 09:42:01 +00:00
ini.GetValue( "Profile", "DisplayName", m_sDisplayName );
2004-02-10 09:42:01 +00:00
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 );
2004-02-23 04:53:02 +00:00
ini.GetValue( "Profile", "WeightPounds", m_iWeightPounds );
2004-02-22 20:58:39 +00:00
ini.GetValue( "Profile", "CaloriesBurned", m_fTotalCaloriesBurned );
2004-04-18 05:46:43 +00:00
ini.GetValue( "Profile", "LastPlayedMachineGuid", m_sLastPlayedMachineGuid );
2004-02-10 09:42:01 +00:00
unsigned i;
for( i=0; i<NUM_PLAY_MODES; i++ )
ini.GetValue( "Profile", "NumSongsPlayedByPlayMode"+Capitalize(PlayModeToString((PlayMode)i)), m_iNumSongsPlayedByPlayMode[i] );
for( i=0; i<NUM_STYLES; i++ )
ini.GetValue( "Profile", "NumSongsPlayedByStyle"+Capitalize(GAMEMAN->GetGameDefForGame(GAMEMAN->GetStyleDefForStyle((Style)i)->m_Game)->m_szName)+Capitalize(GAMEMAN->GetStyleDefForStyle((Style)i)->m_szName), m_iNumSongsPlayedByStyle[i] );
for( i=0; i<NUM_DIFFICULTIES; i++ )
ini.GetValue( "Profile", "NumSongsPlayedByDifficulty"+Capitalize(DifficultyToString((Difficulty)i)), m_iNumSongsPlayedByDifficulty[i] );
for( i=0; i<MAX_METER+1; i++ )
ini.GetValue( "Profile", "NumSongsPlayedByMeter"+ssprintf("%d",i), m_iNumSongsPlayedByMeter[i] );
}
2004-02-22 02:41:10 +00:00
void Profile::SaveEditableDataToDir( CString sDir ) const
2004-02-10 09:42:01 +00:00
{
2004-02-22 02:41:10 +00:00
IniFile ini;
CString fn = sDir + EDITABLE_INI;
ini.SetPath( fn );
2004-02-10 09:42:01 +00:00
2004-02-22 02:41:10 +00:00
ini.SetValue( "Editable", "DisplayName", m_sDisplayName );
ini.SetValue( "Editable", "LastUsedHighScoreName", m_sLastUsedHighScoreName );
2004-02-23 04:53:02 +00:00
ini.SetValue( "Editable", "WeightPounds", m_iWeightPounds );
2004-02-22 02:41:10 +00:00
ini.WriteFile();
}
2004-02-10 09:42:01 +00:00
XNode* Profile::SaveGeneralDataCreateNode() const
{
XNode* pGeneralDataNode = new XNode;
pGeneralDataNode->name = "GeneralData";
2004-02-10 09:42:01 +00:00
2004-04-18 05:46:43 +00:00
pGeneralDataNode->AppendChild( "Guid", m_sGuid );
pGeneralDataNode->AppendChild( "UsingProfileDefaultModifiers", m_bUsingProfileDefaultModifiers );
pGeneralDataNode->AppendChild( "DefaultModifiers", m_sDefaultModifiers );
2004-03-13 19:20:24 +00:00
pGeneralDataNode->AppendChild( "SortOrder", SortOrderToString(m_SortOrder) );
2004-03-25 09:54:28 +00:00
pGeneralDataNode->AppendChild( "LastDifficulty", DifficultyToString(m_LastDifficulty) );
pGeneralDataNode->AppendChild( "LastCourseDifficulty", CourseDifficultyToString(m_LastCourseDifficulty) );
if( m_pLastSong ) pGeneralDataNode->AppendChild( "LastSong", m_pLastSong->GetSongDir() );
pGeneralDataNode->AppendChild( "TotalPlays", m_iTotalPlays );
pGeneralDataNode->AppendChild( "TotalPlaySeconds", m_iTotalPlaySeconds );
pGeneralDataNode->AppendChild( "TotalGameplaySeconds", m_iTotalGameplaySeconds );
pGeneralDataNode->AppendChild( "CurrentCombo", m_iCurrentCombo );
2004-02-22 20:58:39 +00:00
pGeneralDataNode->AppendChild( "TotalCaloriesBurned", m_fTotalCaloriesBurned );
2004-04-18 05:46:43 +00:00
pGeneralDataNode->AppendChild( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid );
pGeneralDataNode->AppendChild( "TotalDancePoints", m_iTotalDancePoints );
pGeneralDataNode->AppendChild( "NumExtraStagesPassed", m_iNumExtraStagesPassed );
pGeneralDataNode->AppendChild( "NumExtraStagesFailed", m_iNumExtraStagesFailed );
pGeneralDataNode->AppendChild( "NumToasties", m_iNumToasties );
2004-02-22 19:54:59 +00:00
pGeneralDataNode->AppendChild( "TotalTapsAndHolds", m_iTotalTapsAndHolds );
2004-02-22 20:58:39 +00:00
pGeneralDataNode->AppendChild( "TotalJumps", m_iTotalJumps );
pGeneralDataNode->AppendChild( "TotalHolds", m_iTotalHolds );
pGeneralDataNode->AppendChild( "TotalMines", m_iTotalMines );
pGeneralDataNode->AppendChild( "TotalHands", m_iTotalHands );
{
XNode* pUnlockedSongs = pGeneralDataNode->AppendChild("UnlockedSongs");
for( set<int>::const_iterator it = m_UnlockedSongs.begin(); it != m_UnlockedSongs.end(); ++it )
pUnlockedSongs->AppendChild( ssprintf("Unlock%i", *it) );
}
{
XNode* pNumSongsPlayedByPlayMode = pGeneralDataNode->AppendChild("NumSongsPlayedByPlayMode");
FOREACH_PlayMode( pm )
2004-02-22 02:51:27 +00:00
{
/* Don't save unplayed PlayModes. */
if( !m_iNumSongsPlayedByPlayMode[pm] )
continue;
pNumSongsPlayedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] );
2004-02-22 02:51:27 +00:00
}
}
{
XNode* pNumSongsPlayedByStyle = pGeneralDataNode->AppendChild("NumSongsPlayedByStyle");
for( int i=0; i<NUM_STYLES; i++ )
{
2004-02-22 02:51:27 +00:00
/* Don't save unplayed styles. */
if( !m_iNumSongsPlayedByStyle[i] )
continue;
XNode *pStyleNode = pNumSongsPlayedByStyle->AppendChild( "Style", m_iNumSongsPlayedByStyle[i] );
const StyleDef *pStyle = GAMEMAN->GetStyleDefForStyle((Style)i);
const GameDef *g = GAMEMAN->GetGameDefForGame( pStyle->m_Game );
ASSERT( g );
pStyleNode->AppendAttr( "Game", g->m_szName );
pStyleNode->AppendAttr( "Style", pStyle->m_szName );
}
}
{
XNode* pNumSongsPlayedByDifficulty = pGeneralDataNode->AppendChild("NumSongsPlayedByDifficulty");
FOREACH_Difficulty( dc )
2004-02-22 04:20:20 +00:00
{
if( !m_iNumSongsPlayedByDifficulty[dc] )
continue;
pNumSongsPlayedByDifficulty->AppendChild( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] );
2004-02-22 04:20:20 +00:00
}
}
{
XNode* pNumSongsPlayedByMeter = pGeneralDataNode->AppendChild("NumSongsPlayedByMeter");
for( int i=0; i<MAX_METER+1; i++ )
2004-02-22 04:20:20 +00:00
{
if( !m_iNumSongsPlayedByMeter[i] )
continue;
pNumSongsPlayedByMeter->AppendChild( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] );
2004-02-22 04:20:20 +00:00
}
}
{
XNode* pNumSongsPassedByGrade = pGeneralDataNode->AppendChild("NumSongsPassedByGrade");
FOREACH_Grade( g )
{
if( !m_iNumSongsPassedByGrade[g] )
continue;
pNumSongsPassedByGrade->AppendChild( GradeToString(g), m_iNumSongsPassedByGrade[g] );
}
}
{
XNode* pNumSongsPassedByPlayMode = pGeneralDataNode->AppendChild("NumSongsPassedByPlayMode");
FOREACH_PlayMode( pm )
{
/* Don't save unplayed PlayModes. */
if( !m_iNumSongsPassedByPlayMode[pm] )
continue;
pNumSongsPassedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumSongsPassedByPlayMode[pm] );
}
}
2004-02-22 20:44:33 +00:00
return pGeneralDataNode;
}
2004-02-22 02:41:10 +00:00
void Profile::LoadEditableDataFromDir( CString sDir )
{
2004-02-22 02:41:10 +00:00
CString fn = sDir + EDITABLE_INI;
//
// Don't load unreasonably large editable.xml files.
//
int iBytes = FILEMAN->GetFileSizeInBytes( fn );
if( iBytes > REASONABLE_EDITABLE_INI_SIZE_BYTES )
{
LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() );
return;
}
2004-02-22 02:41:10 +00:00
IniFile ini;
ini.SetPath( fn );
ini.ReadFile();
ini.GetValue("Editable","DisplayName", m_sDisplayName);
ini.GetValue("Editable","LastUsedHighScoreName", m_sLastUsedHighScoreName);
2004-02-23 04:53:02 +00:00
ini.GetValue("Editable","WeightPounds", m_iWeightPounds);
// This is data that the user can change, so we have to validate it.
wstring wstr = CStringToWstring(m_sDisplayName);
if( wstr.size() > 12 )
wstr = wstr.substr(0, 12);
m_sDisplayName = WStringToCString(wstr);
// TODO: strip invalid chars?
2004-02-23 04:53:02 +00:00
if( m_iWeightPounds != 0 )
CLAMP( m_iWeightPounds, 50, 500 );
2004-02-10 09:42:01 +00:00
}
void Profile::LoadGeneralDataFromNode( const XNode* pNode )
{
ASSERT( pNode->name == "GeneralData" );
2004-03-13 22:18:09 +00:00
CString s;
2004-04-18 05:46:43 +00:00
pNode->GetChildValue( "Guid", m_sGuid );
pNode->GetChildValue( "UsingProfileDefaultModifiers", m_bUsingProfileDefaultModifiers );
pNode->GetChildValue( "DefaultModifiers", m_sDefaultModifiers );
2004-03-13 22:18:09 +00:00
pNode->GetChildValue( "SortOrder", s ); m_SortOrder = StringToSortOrder( s );
2004-03-25 09:54:28 +00:00
pNode->GetChildValue( "LastDifficulty", s ); m_LastDifficulty = StringToDifficulty( s );
pNode->GetChildValue( "LastCourseDifficulty", s ); m_LastCourseDifficulty = StringToCourseDifficulty( s );
pNode->GetChildValue( "LastSong", s ); m_pLastSong = SONGMAN->GetSongFromDir(s);
pNode->GetChildValue( "TotalPlays", m_iTotalPlays );
pNode->GetChildValue( "TotalPlaySeconds", m_iTotalPlaySeconds );
pNode->GetChildValue( "TotalGameplaySeconds", m_iTotalGameplaySeconds );
pNode->GetChildValue( "CurrentCombo", m_iCurrentCombo );
2004-02-22 20:58:39 +00:00
pNode->GetChildValue( "TotalCaloriesBurned", m_fTotalCaloriesBurned );
2004-04-18 05:46:43 +00:00
pNode->GetChildValue( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid );
pNode->GetChildValue( "TotalDancePoints", m_iTotalDancePoints );
pNode->GetChildValue( "NumExtraStagesPassed", m_iNumExtraStagesPassed );
pNode->GetChildValue( "NumExtraStagesFailed", m_iNumExtraStagesFailed );
pNode->GetChildValue( "NumToasties", m_iNumToasties );
2004-02-22 19:54:59 +00:00
pNode->GetChildValue( "TotalTapsAndHolds", m_iTotalTapsAndHolds );
pNode->GetChildValue( "TotalJumps", m_iTotalJumps );
pNode->GetChildValue( "TotalHolds", m_iTotalHolds );
pNode->GetChildValue( "TotalMines", m_iTotalMines );
pNode->GetChildValue( "TotalHands", m_iTotalHands );
{
XNode* pUnlockedSongs = pNode->GetChild("UnlockedSongs");
if( pUnlockedSongs )
{
FOREACH_Node( pUnlockedSongs, song )
{
int iUnlock;
if( sscanf(song->name.c_str(),"Unlock%d",&iUnlock) == 1 )
m_UnlockedSongs.insert( iUnlock );
}
}
}
{
XNode* pNumSongsPlayedByPlayMode = pNode->GetChild("NumSongsPlayedByPlayMode");
if( pNumSongsPlayedByPlayMode )
FOREACH_PlayMode( pm )
pNumSongsPlayedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] );
}
{
XNode* pNumSongsPlayedByStyle = pNode->GetChild("NumSongsPlayedByStyle");
if( pNumSongsPlayedByStyle )
2004-02-22 07:07:03 +00:00
{
FOREACH_Node( pNumSongsPlayedByStyle, style )
{
2004-02-22 07:07:03 +00:00
if( style->name != "Style" )
continue;
2004-02-22 05:26:02 +00:00
CString sGame;
2004-02-22 07:07:03 +00:00
if( !style->GetAttrValue( "Game", sGame ) )
WARN_AND_CONTINUE;
2004-02-22 05:26:02 +00:00
Game g = GAMEMAN->StringToGameType( sGame );
if( g == GAME_INVALID )
WARN_AND_CONTINUE;
2004-02-22 05:26:02 +00:00
CString sStyle;
2004-02-22 07:07:03 +00:00
if( !style->GetAttrValue( "Style", sStyle ) )
WARN_AND_CONTINUE;
2004-02-22 05:26:02 +00:00
Style s = GAMEMAN->GameAndStringToStyle( g, sStyle );
2004-02-22 10:10:00 +00:00
if( s == STYLE_INVALID )
WARN_AND_CONTINUE;
2004-02-22 07:07:03 +00:00
style->GetValue( m_iNumSongsPlayedByStyle[s] );
}
2004-02-22 07:07:03 +00:00
}
}
{
XNode* pNumSongsPlayedByDifficulty = pNode->GetChild("NumSongsPlayedByDifficulty");
if( pNumSongsPlayedByDifficulty )
FOREACH_Difficulty( dc )
pNumSongsPlayedByDifficulty->GetChildValue( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] );
}
{
XNode* pNumSongsPlayedByMeter = pNode->GetChild("NumSongsPlayedByMeter");
if( pNumSongsPlayedByMeter )
for( int i=0; i<MAX_METER+1; i++ )
pNumSongsPlayedByMeter->GetChildValue( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] );
}
{
XNode* pNumSongsPassedByGrade = pNode->GetChild("NumSongsPassedByGrade");
if( pNumSongsPassedByGrade )
FOREACH_Grade( g )
pNumSongsPassedByGrade->GetChildValue( GradeToString(g), m_iNumSongsPassedByGrade[g] );
}
{
XNode* pNumSongsPassedByPlayMode = pNode->GetChild("NumSongsPassedByPlayMode");
if( pNumSongsPassedByPlayMode )
FOREACH_PlayMode( pm )
pNumSongsPassedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumSongsPassedByPlayMode[pm] );
}
2004-02-22 20:44:33 +00:00
}
2004-02-22 19:54:59 +00:00
void Profile::AddStepTotals( int iTotalTapsAndHolds, int iTotalJumps, int iTotalHolds, int iTotalMines, int iTotalHands )
2004-02-22 19:51:46 +00:00
{
2004-02-22 19:54:59 +00:00
m_iTotalTapsAndHolds += iTotalTapsAndHolds;
m_iTotalJumps += iTotalJumps;
m_iTotalHolds += iTotalHolds;
m_iTotalMines += iTotalMines;
m_iTotalHands += iTotalHands;
2004-02-22 19:51:46 +00:00
2004-02-23 04:53:02 +00:00
if( m_iWeightPounds != 0 )
2004-02-22 19:51:46 +00:00
{
float fCals =
2004-02-23 04:53:02 +00:00
SCALE( m_iWeightPounds, 100.f, 200.f, 0.029f, 0.052f ) * iTotalTapsAndHolds +
SCALE( m_iWeightPounds, 100.f, 200.f, 0.111f, 0.193f ) * iTotalJumps +
SCALE( m_iWeightPounds, 100.f, 200.f, 0.029f, 0.052f ) * iTotalHolds +
SCALE( m_iWeightPounds, 100.f, 200.f, 0.000f, 0.000f ) * iTotalMines +
SCALE( m_iWeightPounds, 100.f, 200.f, 0.222f, 0.386f ) * iTotalHands;
2004-02-22 20:58:39 +00:00
m_fTotalCaloriesBurned += fCals;
2004-02-22 23:29:13 +00:00
2004-02-23 01:44:04 +00:00
tm cur_tm = GetLocalTime();
Day day = { cur_tm.tm_yday, cur_tm.tm_year+1900 };
2004-02-22 23:29:13 +00:00
m_mapDayToCaloriesBurned[day] += fCals;
2004-02-22 19:51:46 +00:00
}
}
XNode* Profile::SaveSongScoresCreateNode() const
2004-02-10 09:42:01 +00:00
{
CHECKPOINT;
2004-02-10 10:06:34 +00:00
const Profile* pProfile = this;
2004-02-10 09:42:01 +00:00
ASSERT( pProfile );
XNode* pNode = new XNode;
pNode->name = "SongScores";
2004-02-10 09:42:01 +00:00
for( std::map<SongID,HighScoresForASong>::const_iterator i = m_SongHighScores.begin();
i != m_SongHighScores.end();
i++ )
{
const SongID &songID = i->first;
const HighScoresForASong &hsSong = i->second;
2004-02-10 09:42:01 +00:00
// skip songs that have never been played
if( pProfile->GetSongNumTimesPlayed(songID) == 0 )
2004-02-10 09:42:01 +00:00
continue;
LPXNode pSongNode = pNode->AppendChild( songID.CreateNode() );
2004-02-10 09:42:01 +00:00
for( std::map<StepsID,HighScoresForASteps>::const_iterator j = hsSong.m_StepsHighScores.begin();
j != hsSong.m_StepsHighScores.end();
j++ )
{
const StepsID &stepsID = j->first;
const HighScoresForASteps &hsSteps = j->second;
const HighScoreList &hsl = hsSteps.hs;
2004-02-10 09:42:01 +00:00
// skip steps that have never been played
if( hsl.iNumTimesPlayed == 0 )
2004-02-10 09:42:01 +00:00
continue;
LPXNode pStepsNode = pSongNode->AppendChild( stepsID.CreateNode() );
2004-02-10 09:42:01 +00:00
pStepsNode->AppendChild( hsl.CreateNode() );
}
}
return pNode;
2004-02-10 09:42:01 +00:00
}
void Profile::LoadSongScoresFromNode( const XNode* pNode )
2004-02-10 09:42:01 +00:00
{
ASSERT( pNode->name == "SongScores" );
2004-02-10 09:42:01 +00:00
for( XNodes::const_iterator song = pNode->childs.begin();
song != pNode->childs.end();
2004-02-10 09:42:01 +00:00
song++ )
{
if( (*song)->name != "Song" )
continue;
SongID songID;
songID.LoadFromNode( *song );
if( !songID.IsValid() )
2004-02-10 09:42:01 +00:00
WARN_AND_CONTINUE;
for( XNodes::iterator steps = (*song)->childs.begin();
steps != (*song)->childs.end();
steps++ )
{
if( (*steps)->name != "Steps" )
continue;
StepsID stepsID;
stepsID.LoadFromNode( *steps );
if( !stepsID.IsValid() )
2004-02-10 09:42:01 +00:00
WARN_AND_CONTINUE;
XNode *pHighScoreListNode = (*steps)->GetChild("HighScoreList");
if( pHighScoreListNode == NULL )
WARN_AND_CONTINUE;
HighScoreList &hsl = m_SongHighScores[songID].m_StepsHighScores[stepsID].hs;
2004-02-10 09:42:01 +00:00
hsl.LoadFromNode( pHighScoreListNode );
}
}
}
2004-02-23 21:27:04 +00:00
static Grade ConvertA12Grade( Grade g )
{
/*
* Map
* GRADE_NO_DATA=0, GRADE_E, GRADE_D, GRADE_C, GRADE_B, GRADE_A,
* GRADE_AA,GRADE_AAA,GRADE_AAAA, NUM_GRADES
*
* to GRADE_TIER_1 (AAAA) ... GRADE_TIER_7 (D), GRADE_FAILED (E), GRADE_NO_DATA.
*/
switch( g )
{
case 0: return GRADE_NO_DATA;
case 1: return GRADE_FAILED; /* E */
case 2: return GRADE_TIER_7; /* D */
case 3: return GRADE_TIER_6; /* C */
case 4: return GRADE_TIER_5; /* B */
case 5: return GRADE_TIER_4; /* A */
case 6: return GRADE_TIER_3; /* AA */
case 7: return GRADE_TIER_2; /* AAA */
case 8: return GRADE_TIER_1; /* AAAA */
default: return GRADE_NO_DATA;
}
}
2004-02-10 09:42:01 +00:00
void Profile::LoadSongScoresFromDirSM390a12( CString sDir )
{
Profile* pProfile = this;
ASSERT( pProfile );
CString fn = sDir + SM_390A12_SONG_SCORES_DAT;
if( !IsAFile(fn) )
return;
2004-02-10 09:42:01 +00:00
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; s<iNumSongs; s++ )
{
CString sSongDir;
if( !FileRead(f, sSongDir) )
WARN_AND_RETURN;
Song* pSong = SONGMAN->GetSongFromDir( sSongDir );
int iNumNotes;
if( !FileRead(f, iNumNotes) )
WARN_AND_RETURN;
for( int n=0; n<iNumNotes; n++ )
{
StepsType st;
if( !FileRead(f, (int&)st) )
WARN_AND_RETURN;
Difficulty dc;
if( !FileRead(f, (int&)dc) )
WARN_AND_RETURN;
CString sDescription;
if( !FileRead(f, sDescription) )
WARN_AND_RETURN;
// Even if pSong or pSteps is null, we still have to skip over that data.
Steps* pSteps = NULL;
if( pSong )
{
if( dc==DIFFICULTY_INVALID )
pSteps = pSong->GetStepsByDescription( st, sDescription );
else
pSteps = pSong->GetStepsByDifficulty( st, dc );
}
int iNumTimesPlayed;
if( !FileRead(f, iNumTimesPlayed) )
WARN_AND_RETURN;
HighScoreList &hsl = pProfile->GetStepsHighScoreList(pSong,pSteps);
2004-02-10 09:42:01 +00:00
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; l<iNumHighScores; l++ )
{
CString sName;
if( !FileRead(f, sName) )
WARN_AND_RETURN;
Grade grade;
if( !FileRead(f, (int&)grade) )
WARN_AND_RETURN;
2004-02-23 21:27:04 +00:00
grade = ConvertA12Grade( grade );
2004-02-10 09:42:01 +00:00
int iScore;
if( !FileRead(f, iScore) )
WARN_AND_RETURN;
float fPercentDP;
if( !FileRead(f, fPercentDP) )
WARN_AND_RETURN;
2004-02-23 23:12:40 +00:00
if( grade == NUM_GRADES || grade == GRADE_NO_DATA )
continue; // ignore this high score
2004-02-10 09:42:01 +00:00
if( pSteps == NULL )
continue; // ignore this high score
hsl.vHighScores[l].sName = sName;
hsl.vHighScores[l].grade = grade;
hsl.vHighScores[l].iScore = iScore;
hsl.vHighScores[l].fPercentDP = fPercentDP;
}
// ignore all high scores that are 0
for( l=0; l<iNumHighScores; l++ )
{
if( pSteps && hsl.vHighScores[l].iScore <= 0 )
{
hsl.vHighScores.resize(l);
break;
}
}
}
}
}
void Profile::LoadCategoryScoresFromDirSM390a12( CString sDir )
{
CHECKPOINT;
Profile* pProfile = this;
ASSERT( pProfile );
CString fn = sDir + SM_390A12_CATEGORY_SCORES_DAT;
if( !IsAFile(fn) )
return;
2004-02-10 09:42:01 +00:00
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_CATEGORY_RANKING_VERSION )
WARN_AND_RETURN;
2004-02-23 21:27:04 +00:00
/* NUM_STEPS_TYPES changed after A12. Only read up to the last unchanged type. We
* can probably drop all of this compatibility code before the final release, anyway ... */
for( int st=0; st<=STEPS_TYPE_DS3DDX_SINGLE; st++ )
2004-02-10 09:42:01 +00:00
{
for( int rc=0; rc<NUM_RANKING_CATEGORIES; rc++ )
{
int iNumHighScores;
if( !FileRead(f, iNumHighScores) )
WARN_AND_RETURN;
HighScoreList &hsl = pProfile->GetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc );
hsl.vHighScores.resize( iNumHighScores );
for( int l=0; l<iNumHighScores; l++ )
{
CString sName;
if( !FileRead(f, sName) )
WARN_AND_RETURN;
int iScore;
if( !FileRead(f, iScore) )
WARN_AND_RETURN;
float fPercentDP;
if( !FileRead(f, fPercentDP) )
WARN_AND_RETURN;
hsl.vHighScores[l].sName = sName;
hsl.vHighScores[l].iScore = iScore;
hsl.vHighScores[l].fPercentDP = fPercentDP;
}
}
}
}
void Profile::LoadCourseScoresFromDirSM390a12( CString sDir )
{
CHECKPOINT;
Profile* pProfile = this;
ASSERT( pProfile );
CString fn = sDir + SM_390A12_COURSE_SCORES_DAT;
if( !IsAFile(fn) )
return;
2004-02-10 09:42:01 +00:00
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_COURSE_SCORES_VERSION )
WARN_AND_RETURN;
int iNumCourses;
if( !FileRead(f, iNumCourses) )
WARN_AND_RETURN;
for( int c=0; c<iNumCourses; c++ )
{
CString sPath;
if( !FileRead(f, sPath) )
WARN_AND_RETURN;
Course* pCourse = SONGMAN->GetCourseFromPath( 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;
2004-02-22 06:04:01 +00:00
HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, (StepsType)st, COURSE_DIFFICULTY_REGULAR );
2004-02-10 09:42:01 +00:00
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<iNumHighScores; l++ )
{
CString sName;
if( !FileRead(f, sName) )
WARN_AND_RETURN;
int iScore;
if( !FileRead(f, iScore) )
WARN_AND_RETURN;
float fPercentDP;
if( !FileRead(f, fPercentDP) )
WARN_AND_RETURN;
float fSurviveSeconds;
if( !FileRead(f, fSurviveSeconds) )
WARN_AND_RETURN;
if( pCourse && st < NUM_STEPS_TYPES )
{
hsl.vHighScores[l].sName = sName;
hsl.vHighScores[l].iScore = iScore;
hsl.vHighScores[l].fPercentDP = fPercentDP;
hsl.vHighScores[l].fSurviveSeconds = fSurviveSeconds;
}
}
}
}
}
XNode* Profile::SaveCourseScoresCreateNode() const
2004-02-10 09:42:01 +00:00
{
CHECKPOINT;
2004-02-10 10:06:34 +00:00
const Profile* pProfile = this;
2004-02-10 09:42:01 +00:00
ASSERT( pProfile );
XNode* pNode = new XNode;
pNode->name = "CourseScores";
2004-02-10 09:42:01 +00:00
vector<Course*> vpCourses;
SONGMAN->GetAllCourses( vpCourses, true );
for( unsigned c=0; c<vpCourses.size(); c++ ) // foreach course
{
Course *pCourse = vpCourses[c];
// skip courses that have never been played
if( pProfile->GetCourseNumTimesPlayed(pCourse) == 0 )
continue;
2004-02-22 04:01:12 +00:00
XNode* pCourseNode = pNode->AppendChild( "Course" );
pCourseNode->AppendAttr( "Name", pCourse->m_bIsAutogen ? pCourse->m_sName : pCourse->m_sPath );
2004-02-10 09:42:01 +00:00
for( StepsType st=(StepsType)0; st<NUM_STEPS_TYPES; ((int&)st)++ )
{
2004-02-22 06:04:01 +00:00
// TRICKY: Only append this node if one we have at least one child.
// There's no point to saving a bunch of empty StepsType nodes.
LPXNode pStepsTypeNode = new XNode;
pStepsTypeNode->name = "StepsType";
2004-02-22 04:01:12 +00:00
pStepsTypeNode->AppendAttr( "Type", GameManager::NotesTypeToString(st) );
2004-02-10 09:42:01 +00:00
2004-02-22 06:04:01 +00:00
FOREACH_CourseDifficulty( cd )
{
// skip course/steps types that have never been played
if( pProfile->GetCourseHighScoreList(pCourse, st, cd).iNumTimesPlayed == 0 )
continue;
LPXNode pCourseDifficultyNode = pStepsTypeNode->AppendChild( "CourseDifficulty" );
pCourseDifficultyNode->AppendAttr( "Type", CourseDifficultyToString(cd) );
const HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, st, cd );
pCourseDifficultyNode->AppendChild( hsl.CreateNode() );
}
if( !pStepsTypeNode->childs.empty() )
pCourseNode->AppendChild( pStepsTypeNode );
2004-02-10 09:42:01 +00:00
}
}
return pNode;
2004-02-10 09:42:01 +00:00
}
void Profile::LoadCourseScoresFromNode( const XNode* pNode )
2004-02-10 09:42:01 +00:00
{
CHECKPOINT;
ASSERT( pNode->name == "CourseScores" );
2004-02-10 09:42:01 +00:00
for( XNodes::const_iterator course = pNode->childs.begin();
course != pNode->childs.end();
2004-02-10 09:42:01 +00:00
course++ )
{
if( (*course)->name != "Course" )
continue;
2004-02-22 04:01:12 +00:00
2004-02-22 05:26:02 +00:00
CString sCourse;
if( !(*course)->GetAttrValue( "Name", sCourse ) )
2004-02-22 04:01:12 +00:00
WARN_AND_CONTINUE;
2004-02-10 09:42:01 +00:00
Course* pCourse = SONGMAN->GetCourseFromPath( sCourse );
if( pCourse == NULL )
pCourse = SONGMAN->GetCourseFromName( sCourse );
2004-02-22 04:01:12 +00:00
if( pCourse == NULL )
2004-02-26 20:42:52 +00:00
{
LOG->Trace("Couldn't find course \"%s\"; scoring data will be lost", sCourse.c_str() );
continue;
}
2004-02-10 09:42:01 +00:00
for( XNodes::iterator stepsType = (*course)->childs.begin();
stepsType != (*course)->childs.end();
stepsType++ )
{
if( (*stepsType)->name != "StepsType" )
continue;
2004-02-22 05:26:02 +00:00
CString str;
if( !(*stepsType)->GetAttrValue( "Type", str ) )
2004-02-22 04:01:12 +00:00
WARN_AND_CONTINUE;
2004-02-22 05:26:02 +00:00
StepsType st = GameManager::StringToNotesType( str );
2004-02-22 04:01:12 +00:00
if( st == STEPS_TYPE_INVALID )
WARN_AND_CONTINUE;
2004-02-10 09:42:01 +00:00
2004-02-22 06:04:01 +00:00
for( XNodes::iterator courseDifficulty = (*stepsType)->childs.begin();
courseDifficulty != (*stepsType)->childs.end();
courseDifficulty++ )
{
const LPXAttr TypeAttr = (*courseDifficulty)->GetAttr( "Type" );
if( TypeAttr == NULL )
WARN_AND_CONTINUE;
CourseDifficulty cd = StringToCourseDifficulty( TypeAttr->value );
if( cd == COURSE_DIFFICULTY_INVALID )
WARN_AND_CONTINUE;
XNode *pHighScoreListNode = (*courseDifficulty)->GetChild("HighScoreList");
if( pHighScoreListNode == NULL )
WARN_AND_CONTINUE;
HighScoreList &hsl = this->GetCourseHighScoreList( pCourse, st, cd );
hsl.LoadFromNode( pHighScoreListNode );
}
2004-02-10 09:42:01 +00:00
}
}
}
XNode* Profile::SaveCategoryScoresCreateNode() const
2004-02-10 09:42:01 +00:00
{
CHECKPOINT;
2004-02-10 10:06:34 +00:00
const Profile* pProfile = this;
2004-02-10 09:42:01 +00:00
ASSERT( pProfile );
XNode* pNode = new XNode;
pNode->name = "CategoryScores";
2004-02-10 09:42:01 +00:00
FOREACH_StepsType( st )
{
// skip steps types that have never been played
if( pProfile->GetCategoryNumTimesPlayed( st ) == 0 )
continue;
XNode* pStepsTypeNode = pNode->AppendChild( "StepsType" );
pStepsTypeNode->AppendAttr( "Type", GameManager::NotesTypeToString(st) );
2004-02-10 09:42:01 +00:00
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" );
pRankingCategoryNode->AppendAttr( "Type", RankingCategoryToString(rc) );
2004-02-10 09:42:01 +00:00
2004-02-10 10:06:34 +00:00
const HighScoreList &hsl = pProfile->GetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc );
2004-02-10 09:42:01 +00:00
pRankingCategoryNode->AppendChild( hsl.CreateNode() );
}
}
return pNode;
2004-02-10 09:42:01 +00:00
}
void Profile::LoadCategoryScoresFromNode( const XNode* pNode )
2004-02-10 09:42:01 +00:00
{
CHECKPOINT;
ASSERT( pNode->name == "CategoryScores" );
2004-02-15 04:47:32 +00:00
for( XNodes::const_iterator stepsType = pNode->childs.begin();
stepsType != pNode->childs.end();
2004-02-10 09:42:01 +00:00
stepsType++ )
{
if( (*stepsType)->name != "StepsType" )
continue;
2004-02-22 05:26:02 +00:00
CString str;
if( !(*stepsType)->GetAttrValue( "Type", str ) )
WARN_AND_CONTINUE;
2004-02-22 05:26:02 +00:00
StepsType st = GameManager::StringToNotesType( str );
if( st == STEPS_TYPE_INVALID )
WARN_AND_CONTINUE;
2004-02-10 09:42:01 +00:00
for( XNodes::iterator radarCategory = (*stepsType)->childs.begin();
radarCategory != (*stepsType)->childs.end();
radarCategory++ )
{
if( (*radarCategory)->name != "RankingCategory" )
continue;
2004-02-22 05:26:02 +00:00
if( !(*radarCategory)->GetAttrValue( "Type", str ) )
WARN_AND_CONTINUE;
2004-02-22 05:26:02 +00:00
RankingCategory rc = StringToRankingCategory( str );
if( rc == RANKING_INVALID )
WARN_AND_CONTINUE;
2004-02-10 09:42:01 +00:00
XNode *pHighScoreListNode = (*radarCategory)->GetChild("HighScoreList");
if( pHighScoreListNode == NULL )
WARN_AND_CONTINUE;
HighScoreList &hsl = this->GetCategoryHighScoreList( st, rc );
hsl.LoadFromNode( pHighScoreListNode );
}
}
}
void Profile::DeleteProfileDataFromDirSM390a12( CString sDir ) const
{
CString fn = sDir + SM_390A12_PROFILE_INI;
FILEMAN->Remove( fn );
}
2004-02-10 10:06:34 +00:00
void Profile::DeleteSongScoresFromDirSM390a12( CString sDir ) const
2004-02-10 09:42:01 +00:00
{
CString fn = sDir + SM_390A12_SONG_SCORES_DAT;
FILEMAN->Remove( fn );
2004-02-10 09:42:01 +00:00
}
2004-02-10 10:06:34 +00:00
void Profile::DeleteCourseScoresFromDirSM390a12( CString sDir ) const
2004-02-10 09:42:01 +00:00
{
CString fn = sDir + SM_390A12_COURSE_SCORES_DAT;
FILEMAN->Remove( fn );
2004-02-10 09:42:01 +00:00
}
2004-02-10 10:06:34 +00:00
void Profile::DeleteCategoryScoresFromDirSM390a12( CString sDir ) const
2004-02-10 09:42:01 +00:00
{
CString fn = sDir + SM_390A12_CATEGORY_SCORES_DAT;
FILEMAN->Remove( fn );
2004-02-10 09:42:01 +00:00
}
2004-02-10 10:06:34 +00:00
void Profile::SaveStatsWebPageToDir( CString sDir ) const
2004-02-10 09:42:01 +00:00
{
ASSERT( PROFILEMAN );
2004-02-23 04:53:02 +00:00
bool bThisIsMachineProfile = (this == PROFILEMAN->GetMachineProfile()); // UGLY
2004-02-22 02:41:10 +00:00
SaveStatsWebPage(
sDir,
this,
PROFILEMAN->GetMachineProfile(),
bThisIsMachineProfile ? HTML_TYPE_MACHINE : HTML_TYPE_PLAYER
);
2004-02-10 09:42:01 +00:00
}
2004-02-16 05:35:06 +00:00
void Profile::SaveMachinePublicKeyToDir( CString sDir ) const
{
if( PREFSMAN->m_bSignProfileData && IsAFile(CRYPTMAN->GetPublicKeyFileName()) )
2004-02-23 08:09:21 +00:00
FileCopy( CRYPTMAN->GetPublicKeyFileName(), sDir+PUBLIC_KEY_FILE );
2004-02-16 05:35:06 +00:00
}
2004-02-17 01:16:57 +00:00
void Profile::AddScreenshot( Screenshot screenshot )
{
m_vScreenshots.push_back( screenshot );
}
void Profile::LoadScreenshotDataFromNode( const XNode* pNode )
2004-02-17 01:16:57 +00:00
{
CHECKPOINT;
ASSERT( pNode->name == "ScreenshotData" );
for( XNodes::const_iterator screenshot = pNode->childs.begin();
screenshot != pNode->childs.end();
2004-02-17 01:16:57 +00:00
screenshot++ )
{
if( (*screenshot)->name != "Screenshot" )
WARN_AND_CONTINUE;
Screenshot ss;
if( !(*screenshot)->GetChildValue("FileName",ss.sFileName) )
2004-02-22 10:10:00 +00:00
WARN;
2004-02-17 01:16:57 +00:00
2004-02-22 02:41:10 +00:00
if( !(*screenshot)->GetChildValue("MD5",ss.sMD5) )
2004-02-22 10:10:00 +00:00
WARN;
2004-02-17 01:16:57 +00:00
2004-02-22 05:04:11 +00:00
if( !(*screenshot)->GetChildValue("Time",(int&)ss.time) ) // time_t is a signed long on Win32. Is this ok on other platforms?
2004-02-22 10:10:00 +00:00
WARN;
2004-04-18 05:46:43 +00:00
if( !(*screenshot)->GetChildValue("MachineGuid",ss.sMachineGuid) )
2004-02-22 10:10:00 +00:00
WARN;
2004-02-17 01:16:57 +00:00
m_vScreenshots.push_back( ss );
}
}
XNode* Profile::SaveScreenshotDataCreateNode() const
2004-02-17 01:16:57 +00:00
{
CHECKPOINT;
const Profile* pProfile = this;
ASSERT( pProfile );
XNode* pNode = new XNode;
pNode->name = "ScreenshotData";
2004-02-17 01:16:57 +00:00
2004-02-17 07:07:13 +00:00
for( unsigned i=0; i<m_vScreenshots.size(); i++ )
2004-02-17 01:16:57 +00:00
{
const Screenshot &ss = m_vScreenshots[i];
XNode* pScreenshotNode = pNode->AppendChild( "Screenshot" );
2004-02-17 01:16:57 +00:00
pScreenshotNode->AppendChild( "FileName", ss.sFileName );
2004-02-22 20:58:39 +00:00
pScreenshotNode->AppendChild( "MD5", ss.sMD5 );
pScreenshotNode->AppendChild( "Time", (int) ss.time );
2004-04-18 05:46:43 +00:00
pScreenshotNode->AppendChild( "MachineGuid", ss.sMachineGuid );
2004-02-17 01:16:57 +00:00
}
return pNode;
2004-02-17 01:16:57 +00:00
}
2004-02-22 23:29:13 +00:00
void Profile::LoadCalorieDataFromNode( const XNode* pNode )
{
CHECKPOINT;
ASSERT( pNode->name == "CalorieData" );
for( XNodes::const_iterator pDay = pNode->childs.begin();
pDay != pNode->childs.end();
pDay++ )
{
if( (*pDay)->name != "Day" )
WARN_AND_CONTINUE;
Day day;
if( !(*pDay)->GetAttrValue("DayInYear",day.iDayInYear) )
WARN_AND_CONTINUE;
if( !(*pDay)->GetAttrValue("Year",day.iYear) )
WARN_AND_CONTINUE;
float fCaloriesBurned = 0;
if( !(*pDay)->GetChildValue("CaloriesBurned",fCaloriesBurned) )
WARN_AND_CONTINUE;
m_mapDayToCaloriesBurned[day] = fCaloriesBurned;
}
}
XNode* Profile::SaveCalorieDataCreateNode() const
{
CHECKPOINT;
const Profile* pProfile = this;
ASSERT( pProfile );
XNode* pNode = new XNode;
pNode->name = "CalorieData";
for( map<Day,float>::const_iterator i = m_mapDayToCaloriesBurned.begin();
i != m_mapDayToCaloriesBurned.end();
i++ )
{
XNode* pDay = pNode->AppendChild( "Day" );
pDay->AppendAttr( "DayInYear", i->first.iDayInYear );
pDay->AppendAttr( "Year", i->first.iYear );
pDay->AppendChild( "CaloriesBurned", i->second );
}
return pNode;
}
float Profile::GetCaloriesBurnedForDay( Day day ) const
{
map<Day,float>::const_iterator i = m_mapDayToCaloriesBurned.find( day );
if( i == m_mapDayToCaloriesBurned.end() )
return 0;
else
return i->second;
}
2004-03-07 04:34:49 +00:00
XNode* Profile::AwardRecord::CreateNode() const
{
XNode* pNode = new XNode;
pNode->name = "AwardRecord";
2004-03-08 00:07:42 +00:00
pNode->AppendChild( "FirstTime", int(first) );
pNode->AppendChild( "LastTime", int(last) );
2004-03-07 04:34:49 +00:00
pNode->AppendChild( "Count", iCount );
return pNode;
}
void Profile::AwardRecord::LoadFromNode( const XNode* pNode )
{
Unset();
ASSERT( pNode->name == "AwardRecord" );
pNode->GetChildValue( "FirstTime", (int&)first ); // time_t is a signed long in Win32. Is this OK on other platforms?
pNode->GetChildValue( "LastTime", (int&)last );
pNode->GetChildValue( "Count", iCount );
}
void Profile::LoadAwardsFromNode( const XNode* pNode )
{
CHECKPOINT;
ASSERT( pNode->name == "Awards" );
for( XNodes::const_iterator pAward = pNode->childs.begin();
pAward != pNode->childs.end();
pAward++ )
{
if( (*pAward)->name == "PerDifficultyAward" )
{
CString sStepsType;
if( !(*pAward)->GetAttrValue( "StepsType", sStepsType ) )
WARN_AND_CONTINUE;
StepsType st = GameManager::StringToNotesType( sStepsType );
if( st == STEPS_TYPE_INVALID )
WARN_AND_CONTINUE;
CString sDifficulty;
if( !(*pAward)->GetAttrValue( "Difficulty", sDifficulty ) )
WARN_AND_CONTINUE;
Difficulty dc = StringToDifficulty( sDifficulty );
if( dc == DIFFICULTY_INVALID )
WARN_AND_CONTINUE;
CString sPerDifficultyAward;
if( !(*pAward)->GetAttrValue( "PerDifficultyAward", sPerDifficultyAward ) )
WARN_AND_CONTINUE;
PerDifficultyAward pda = StringToPerDifficultyAward( sPerDifficultyAward );
if( pda == PER_DIFFICULTY_AWARD_INVALID )
WARN_AND_CONTINUE;
AwardRecord &ar = m_PerDifficultyAwards[st][dc][pda];
XNode* pAwardRecord = (*pAward)->GetChild("AwardRecord");
if( pAwardRecord == NULL )
WARN_AND_CONTINUE;
ar.LoadFromNode( pAwardRecord );
}
else if( (*pAward)->name == "PeakComboAward" )
{
CString sPeakComboAward;
2004-03-14 19:26:03 +00:00
if( !(*pAward)->GetAttrValue( "PeakComboAward", sPeakComboAward ) )
2004-03-07 04:34:49 +00:00
WARN_AND_CONTINUE;
PeakComboAward pca = StringToPeakComboAward( sPeakComboAward );
if( pca == PEAK_COMBO_AWARD_INVALID )
WARN_AND_CONTINUE;
AwardRecord &ar = m_PeakComboAwards[pca];
XNode* pAwardRecord = (*pAward)->GetChild("AwardRecord");
if( pAwardRecord == NULL )
WARN_AND_CONTINUE;
ar.LoadFromNode( pAwardRecord );
}
else
WARN_AND_CONTINUE;
}
}
XNode* Profile::SaveAwardsCreateNode() const
{
CHECKPOINT;
const Profile* pProfile = this;
ASSERT( pProfile );
XNode* pNode = new XNode;
pNode->name = "Awards";
FOREACH_StepsType( st )
{
FOREACH_Difficulty( dc )
{
FOREACH_PerDifficultyAward( pda )
{
const AwardRecord &ar = m_PerDifficultyAwards[st][dc][pda];
if( !ar.IsSet() )
continue;
XNode* pAward = pNode->AppendChild( "PerDifficultyAward" );
pAward->AppendAttr( "StepsType", GameManager::NotesTypeToString(st) );
pAward->AppendAttr( "Difficulty", DifficultyToString(dc) );
pAward->AppendAttr( "PerDifficultyAward", PerDifficultyAwardToString(pda) );
pAward->AppendChild( ar.CreateNode() );
}
}
}
FOREACH_PeakComboAward( pca )
{
const AwardRecord &ar = m_PeakComboAwards[pca];
if( !ar.IsSet() )
continue;
XNode* pAward = pNode->AppendChild( "PeakComboAward" );
pAward->AppendAttr( "PeakComboAward", PeakComboAwardToString(pca) );
pAward->AppendChild( ar.CreateNode() );
}
return pNode;
}
void Profile::AddPerDifficultyAward( StepsType st, Difficulty dc, PerDifficultyAward pda )
{
m_PerDifficultyAwards[st][dc][pda].Set( time(NULL) );
}
void Profile::AddPeakComboAward( PeakComboAward pca )
{
m_PeakComboAwards[pca].Set( time(NULL) );
}
bool Profile::HasPerDifficultyAward( StepsType st, Difficulty dc, PerDifficultyAward pda )
{
return m_PerDifficultyAwards[st][dc][pda].IsSet();
}
bool Profile::HasPeakComboAward( PeakComboAward pca )
{
return m_PeakComboAwards[pca].IsSet();
}