Profile loading no longer clears out scores for songs that can't be found. Profile merging functions added to ProfileMan and ScreenManageProfiles for profile migration. HighScore operators rewritten to follow good practice.

This commit is contained in:
Kyzentun
2014-11-25 21:37:15 -07:00
parent 1a51b06934
commit a580d54662
9 changed files with 274 additions and 12 deletions
+4
View File
@@ -1216,6 +1216,10 @@ Edit=Edit
Rename=Rename
SetDefaultP1=Set P1
SetDefaultP2=Set P2
MergeToMachine=Merge into Machine Profile
MergeToMachineSkipTotal=Merge into Machine Profile Without Totals
MergeToP1=Merge into P1 Profile
MergeToP2=Merge into P2 Profile
[RadarCategory]
Air=Air
+44 -4
View File
@@ -8,6 +8,8 @@
#include "Foreach.h"
#include "RadarValues.h"
#include <algorithm>
ThemeMetric<RString> EMPTY_NAME("HighScore","EmptyName");
@@ -231,22 +233,37 @@ void HighScore::SetDisqualified( bool b ) { m_Impl->bDisqualified = b; }
* is used. */
RString *HighScore::GetNameMutable() { return &m_Impl->sName; }
bool HighScore::operator>=( const HighScore& other ) const
bool HighScore::operator<(HighScore const& other) const
{
/* Make sure we treat AAAA as higher than AAA, even though the score
* is the same. */
if( PREFSMAN->m_bPercentageScoring )
{
if( GetPercentDP() != other.GetPercentDP() )
return GetPercentDP() >= other.GetPercentDP();
return GetPercentDP() < other.GetPercentDP();
}
else
{
if( GetScore() != other.GetScore() )
return GetScore() >= other.GetScore();
return GetScore() < other.GetScore();
}
return GetGrade() >= other.GetGrade();
return GetGrade() < other.GetGrade();
}
bool HighScore::operator>(HighScore const& other) const
{
return other.operator<(*this);
}
bool HighScore::operator<=( const HighScore& other ) const
{
return !operator>(other);
}
bool HighScore::operator>=( const HighScore& other ) const
{
return !operator<(other);
}
bool HighScore::operator==( const HighScore& other ) const
@@ -254,6 +271,11 @@ bool HighScore::operator==( const HighScore& other ) const
return *m_Impl == *other.m_Impl;
}
bool HighScore::operator!=( const HighScore& other ) const
{
return !operator==(other);
}
XNode* HighScore::CreateNode() const
{
return m_Impl->CreateNode();
@@ -406,6 +428,24 @@ void HighScoreList::ClampSize( bool bIsMachine )
vHighScores.erase( vHighScores.begin()+iMaxScores, vHighScores.end() );
}
void HighScoreList::MergeFromOtherHSL(HighScoreList& other, bool is_machine)
{
iNumTimesPlayed+= other.iNumTimesPlayed;
if(other.dtLastPlayed > dtLastPlayed) { dtLastPlayed= other.dtLastPlayed; }
if(other.HighGrade > HighGrade) { HighGrade= other.HighGrade; }
vHighScores.insert(vHighScores.end(), other.vHighScores.begin(),
other.vHighScores.end());
std::sort(vHighScores.begin(), vHighScores.end());
// Remove non-unique scores because they probably come from an accidental
// repeated merge. -Kyz
vector<HighScore>::iterator unique_end=
std::unique(vHighScores.begin(), vHighScores.end());
vHighScores.erase(unique_end, vHighScores.end());
// Reverse it because sort moved the lesser scores to the top.
std::reverse(vHighScores.begin(), vHighScores.end());
ClampSize(is_machine);
}
XNode* Screenshot::CreateNode() const
{
XNode* pNode = new XNode( "Screenshot" );
+17 -2
View File
@@ -88,8 +88,12 @@ struct HighScore
void Unset();
bool operator>=( const HighScore& other ) const;
bool operator==( const HighScore& other ) const;
bool operator<(HighScore const& other) const;
bool operator>(HighScore const& other) const;
bool operator<=(HighScore const& other) const;
bool operator>=(HighScore const& other) const;
bool operator==(HighScore const& other) const;
bool operator!=(HighScore const& other) const;
XNode* CreateNode() const;
void LoadFromNode( const XNode* pNode );
@@ -131,6 +135,8 @@ public:
void RemoveAllButOneOfEachName();
void ClampSize( bool bIsMachine );
void MergeFromOtherHSL(HighScoreList& other, bool is_machine);
XNode* CreateNode() const;
void LoadFromNode( const XNode* pNode );
@@ -158,6 +164,15 @@ struct Screenshot
XNode* CreateNode() const;
void LoadFromNode( const XNode* pNode );
bool operator<(Screenshot const& rhs) const
{
return highScore.GetDateTime() < rhs.highScore.GetDateTime();
}
bool operator==(Screenshot const& rhs) const
{
return sFileName == rhs.sFileName;
}
};
#endif
+146 -6
View File
@@ -26,6 +26,8 @@
#include "CharacterManager.h"
#include "Character.h"
#include <algorithm>
const RString STATS_XML = "Stats.xml";
const RString STATS_XML_GZ = "Stats.xml.gz";
/** @brief The filename for where one can edit their personal profile information. */
@@ -547,7 +549,7 @@ Song *Profile::GetMostPopularSong() const
FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i )
{
int iNumTimesPlayed = i->second.GetNumTimesPlayed();
if( iNumTimesPlayed > iMaxNumTimesPlayed )
if(i->first.ToSong() != NULL && iNumTimesPlayed > iMaxNumTimesPlayed)
{
id = i->first;
iMaxNumTimesPlayed = iNumTimesPlayed;
@@ -564,7 +566,7 @@ Course *Profile::GetMostPopularCourse() const
FOREACHM_CONST( CourseID, HighScoresForACourse, m_CourseHighScores, i )
{
int iNumTimesPlayed = i->second.GetNumTimesPlayed();
if( iNumTimesPlayed > iMaxNumTimesPlayed )
if(i->first.ToCourse() != NULL && iNumTimesPlayed > iMaxNumTimesPlayed)
{
id = i->first;
iMaxNumTimesPlayed = iNumTimesPlayed;
@@ -785,6 +787,140 @@ void Profile::GetAllUsedHighScoreNames(std::set<RString>& names)
#undef GET_NAMES_FROM_MAP
}
// MergeScoresFromOtherProfile has three intended use cases:
// 1. Restoring scores to the machine profile that were deleted because the
// songs were not loaded.
// 2. Migrating a profile from an older version of Stepmania, and adding its
// scores to the machine profile.
// 3. Merging two profiles that were separate together.
// In case 1, the various total numbers are still correct, so they should be
// skipped. This is why the skip_totals arg exists.
// -Kyz
void Profile::MergeScoresFromOtherProfile(Profile* other, bool skip_totals,
RString const& from_dir, RString const& to_dir)
{
if(!skip_totals)
{
#define MERGE_FIELD(field_name) field_name+= other->field_name;
MERGE_FIELD(m_iTotalSessions);
MERGE_FIELD(m_iTotalSessionSeconds);
MERGE_FIELD(m_iTotalGameplaySeconds);
MERGE_FIELD(m_fTotalCaloriesBurned);
MERGE_FIELD(m_iTotalDancePoints);
MERGE_FIELD(m_iNumExtraStagesPassed);
MERGE_FIELD(m_iNumExtraStagesFailed);
MERGE_FIELD(m_iNumToasties);
MERGE_FIELD(m_iTotalTapsAndHolds);
MERGE_FIELD(m_iTotalJumps);
MERGE_FIELD(m_iTotalHolds);
MERGE_FIELD(m_iTotalRolls);
MERGE_FIELD(m_iTotalMines);
MERGE_FIELD(m_iTotalHands);
MERGE_FIELD(m_iTotalLifts);
FOREACH_ENUM(PlayMode, i)
{
MERGE_FIELD(m_iNumSongsPlayedByPlayMode[i]);
MERGE_FIELD(m_iNumStagesPassedByPlayMode[i]);
}
FOREACH_ENUM(Difficulty, i)
{
MERGE_FIELD(m_iNumSongsPlayedByDifficulty[i]);
}
for(int i= 0; i < MAX_METER; ++i)
{
MERGE_FIELD(m_iNumSongsPlayedByMeter[i]);
}
MERGE_FIELD(m_iNumTotalSongsPlayed);
FOREACH_ENUM(Grade, i)
{
MERGE_FIELD(m_iNumStagesPassedByGrade[i]);
}
#undef MERGE_FIELD
for(map<DateTime, Calories>::iterator other_cal=
other->m_mapDayToCaloriesBurned.begin();
other_cal != other->m_mapDayToCaloriesBurned.end(); ++other_cal)
{
map<DateTime, Calories>::iterator this_cal=
m_mapDayToCaloriesBurned.find(other_cal->first);
if(this_cal == m_mapDayToCaloriesBurned.end())
{
m_mapDayToCaloriesBurned[other_cal->first]= other_cal->second;
}
else
{
this_cal->second.fCals+= other_cal->second.fCals;
}
}
}
#define MERGE_SCORES_IN_MEMBER(main_member, main_key_type, main_value_type, sub_member, sub_key_type, sub_value_type) \
for(std::map<main_key_type, main_value_type>::iterator main_entry= \
other->main_member.begin(); main_entry != other->main_member.end(); \
++main_entry) \
{ \
std::map<main_key_type, main_value_type>::iterator this_entry= \
main_member.find(main_entry->first); \
if(this_entry == main_member.end()) \
{ \
main_member[main_entry->first]= main_entry->second; \
} \
else \
{ \
for(std::map<sub_key_type, sub_value_type>::iterator sub_entry= \
main_entry->second.sub_member.begin(); \
sub_entry != main_entry->second.sub_member.end(); ++sub_entry) \
{ \
std::map<sub_key_type, sub_value_type>::iterator this_sub= \
this_entry->second.sub_member.find(sub_entry->first); \
if(this_sub == this_entry->second.sub_member.end()) \
{ \
this_entry->second.sub_member[sub_entry->first]= sub_entry->second; \
} \
else \
{ \
this_sub->second.hsl.MergeFromOtherHSL(sub_entry->second.hsl, IsMachine()); \
} \
} \
} \
}
MERGE_SCORES_IN_MEMBER(m_SongHighScores, SongID, HighScoresForASong, m_StepsHighScores, StepsID, HighScoresForASteps);
MERGE_SCORES_IN_MEMBER(m_CourseHighScores, CourseID, HighScoresForACourse, m_TrailHighScores, TrailID, HighScoresForATrail);
#undef MERGE_SCORES_IN_MEMBER
// I think the machine profile should not have screenshots merged into it
// because the intended use case is someone whose profile scores were
// deleted off the machine by mishap, or a profile being migrated from an
// older version of Stepmania. Either way, the screenshots should stay
// with the profile they came from.
// In the case where two local profiles are being merged together, the user
// is probably planning to delete the old profile after the merge, so we
// want to copy the screenshots over. -Kyz
if(!IsMachine())
{
// The old screenshot count is stored so we know where to start in the
// list when copying the screenshot images.
size_t old_count= m_vScreenshots.size();
m_vScreenshots.insert(m_vScreenshots.end(),
other->m_vScreenshots.begin(), other->m_vScreenshots.end());
for(size_t sid= old_count; sid < m_vScreenshots.size(); ++sid)
{
RString old_path= from_dir + "Screenshots/" + m_vScreenshots[sid].sFileName;
RString new_path= to_dir + "Screenshots/" + m_vScreenshots[sid].sFileName;
// Only move the old screenshot over if it exists and won't stomp an
// existing screenshot.
if(FILEMAN->DoesFileExist(old_path) && (!FILEMAN->DoesFileExist(new_path)))
{
FILEMAN->Move(old_path, new_path);
}
}
// The screenshots are kept sorted by date for ease of use, and
// duplicates are removed because they come from the user mistakenly
// merging a second time. -Kyz
std::sort(m_vScreenshots.begin(), m_vScreenshots.end());
vector<Screenshot>::iterator unique_end=
std::unique(m_vScreenshots.begin(), m_vScreenshots.end());
m_vScreenshots.erase(unique_end, m_vScreenshots.end());
}
}
// Category high scores
void Profile::AddCategoryHighScore( StepsType st, RankingCategory rc, HighScore hs, int &iIndexOut )
{
@@ -1634,8 +1770,10 @@ void Profile::LoadSongScoresFromNode( const XNode* pSongScores )
SongID songID;
songID.LoadFromNode( pSong );
if( !songID.IsValid() )
continue;
// Allow invalid songs so that scores aren't deleted for people that use
// AdditionalSongsFolders and change it frequently. -Kyz
//if( !songID.IsValid() )
// continue;
FOREACH_CONST_Child( pSong, pSteps )
{
@@ -1714,8 +1852,10 @@ void Profile::LoadCourseScoresFromNode( const XNode* pCourseScores )
CourseID courseID;
courseID.LoadFromNode( pCourse );
if( !courseID.IsValid() )
WARN_AND_CONTINUE;
// Allow invalid courses so that scores aren't deleted for people that use
// AdditionalCoursesFolders and change it frequently. -Kyz
//if( !courseID.IsValid() )
// WARN_AND_CONTINUE;
// Backward compatability hack to fix importing scores of old style
+5
View File
@@ -71,6 +71,8 @@ public:
* @brief Set up the Profile with default values.
*
* Note: there are probably a lot of variables. */
// When adding new score related data, add logic for handling it to
// MergeScoresFromOtherProfile. -Kyz
Profile(): m_sDisplayName(""), m_sCharacterID(""),
m_sLastUsedHighScoreName(""), m_iWeightPounds(0),
m_Voomax(0), m_BirthYear(0), m_IgnoreStepCountCalories(false),
@@ -277,6 +279,9 @@ public:
void GetAllUsedHighScoreNames(std::set<RString>& names);
void MergeScoresFromOtherProfile(Profile* other, bool skip_totals,
RString const& from_dir, RString const& to_dir);
// Category high scores
HighScoreList m_CategoryHighScores[NUM_StepsType][NUM_RankingCategory];
+23
View File
@@ -620,6 +620,29 @@ const Profile* ProfileManager::GetProfile( ProfileSlot slot ) const
}
}
void ProfileManager::MergeLocalProfiles(RString const& from_id, RString const& to_id)
{
Profile* from= GetLocalProfile(from_id);
Profile* to= GetLocalProfile(to_id);
if(from == NULL || to == NULL)
{
return;
}
to->MergeScoresFromOtherProfile(from, false,
LocalProfileIDToDir(from_id), LocalProfileIDToDir(to_id));
}
void ProfileManager::MergeLocalProfileIntoMachine(RString const& from_id, bool skip_totals)
{
Profile* from= GetLocalProfile(from_id);
if(from == NULL)
{
return;
}
GetMachineProfile()->MergeScoresFromOtherProfile(from, skip_totals,
LocalProfileIDToDir(from_id), MACHINE_PROFILE_DIR);
}
//
// General
//
+3
View File
@@ -52,6 +52,9 @@ public:
bool SaveLocalProfile( RString sProfileID );
void UnloadProfile( PlayerNumber pn );
void MergeLocalProfiles(RString const& from_id, RString const& to_id);
void MergeLocalProfileIntoMachine(RString const& from_id, bool skip_totals);
// General data
void IncrementToastiesCount( PlayerNumber pn );
void AddStepTotals( PlayerNumber pn, int iNumTapsAndHolds, int iNumJumps, int iNumHolds, int iNumRolls, int iNumMines, int iNumHands, int iNumLifts, float fCaloriesBurned );
+4
View File
@@ -51,6 +51,10 @@ private:
extern const RageTimer RageZeroTimer;
// For profiling how long some chunk of code takes. -Kyz
#define START_TIME(name) float name##_start_time= RageTimer::GetTimeSinceStartFast();
#define END_TIME(name) float name##_end_time= RageTimer::GetTimeSinceStartFast(); LOG->Warn(#name " time: %f", name##_end_time - name##_start_time);
#endif
/*
+28
View File
@@ -30,6 +30,10 @@ enum ProfileAction
ProfileAction_Rename,
ProfileAction_Delete,
ProfileAction_Clear,
ProfileAction_MergeToMachine,
ProfileAction_MergeToMachineSkipTotal,
ProfileAction_MergeToP1,
ProfileAction_MergeToP2,
NUM_ProfileAction
};
static const char *ProfileActionNames[] = {
@@ -39,6 +43,10 @@ static const char *ProfileActionNames[] = {
"Rename",
"Delete",
"Clear",
"MergeToMachine",
"MergeToMachineSkipTotal",
"MergeToP1",
"MergeToP2",
};
XToString( ProfileAction );
XToLocalizedString( ProfileAction );
@@ -309,6 +317,22 @@ void ScreenOptionsManageProfiles::HandleScreenMessage( const ScreenMessage SM )
ScreenPrompt::Prompt( SM_BackFromClearConfirm, sMessage, PROMPT_YES_NO );
}
break;
case ProfileAction_MergeToMachine:
PROFILEMAN->MergeLocalProfileIntoMachine(
GetLocalProfileIDWithFocus(), false);
break;
case ProfileAction_MergeToMachineSkipTotal:
PROFILEMAN->MergeLocalProfileIntoMachine(
GetLocalProfileIDWithFocus(), true);
break;
case ProfileAction_MergeToP1:
PROFILEMAN->MergeLocalProfiles(GetLocalProfileIDWithFocus(),
ProfileManager::m_sDefaultLocalProfileID[PLAYER_1].Get());
break;
case ProfileAction_MergeToP2:
PROFILEMAN->MergeLocalProfiles(GetLocalProfileIDWithFocus(),
ProfileManager::m_sDefaultLocalProfileID[PLAYER_2].Get());
break;
}
}
}
@@ -383,6 +407,10 @@ void ScreenOptionsManageProfiles::ProcessMenuStart( const InputEventPlus & )
ADD_ACTION( ProfileAction_Edit );
ADD_ACTION( ProfileAction_Rename );
ADD_ACTION( ProfileAction_Delete );
ADD_ACTION( ProfileAction_MergeToMachine );
ADD_ACTION( ProfileAction_MergeToMachineSkipTotal );
ADD_ACTION( ProfileAction_MergeToP1 );
ADD_ACTION( ProfileAction_MergeToP2 );
}
int iWidth, iX, iY;