Merged changelog.

This commit is contained in:
Kyzentun
2014-12-08 16:00:59 -07:00
12 changed files with 692 additions and 39 deletions
+12
View File
@@ -4,6 +4,18 @@ The StepMania 5 Changelog covers all post-sm-ssc changes. For a list of changes
from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt.
________________________________________________________________________________
2014/11/28
----------
* [Profile] Guest and Test profile types added. All existing and new
profiles are Normal. If a profile is changed to Guest through the profile
management screen, it will always show at the top of the list. A profile
set to Test will always be at the bottom of the list. Profiles can be
moved around in the list.
GetType and GetPriority lua functions added to Profile.
Bug that caused a crash when a profile dir was renamed to a non-number and
a new profile was created fixed. [kyzentun]
2014/11/22
----------
* [Preferences] MinTNSToHideNotes preference added. [kyzentun]
+7
View File
@@ -1201,6 +1201,7 @@
<Function name='GetMostPopularSong'/>
<Function name='GetNumToasties'/>
<Function name='GetNumTotalSongsPlayed'/>
<Function name='GetPriority'/>
<Function name='GetSongNumTimesPlayed'/>
<Function name='GetSongsActual'/>
<Function name='GetSongsAndCoursesPercentCompleteAllDifficulties'/>
@@ -1220,6 +1221,7 @@
<Function name='GetTotalStepsWithTopGrade'/>
<Function name='GetTotalTapsAndHolds'/>
<Function name='GetTotalTrailsWithTopGrade'/>
<Function name='GetType'/>
<Function name='GetUserTable'/>
<Function name='GetVoomax'/>
<Function name='GetWeightPounds'/>
@@ -2271,6 +2273,11 @@
<EnumValue name='&apos;ProfileSlot_Player2&apos;' value='1'/>
<EnumValue name='&apos;ProfileSlot_Machine&apos;' value='2'/>
</Enum>
<Enum name='ProfileType'>
<EnumValue name='&apos;ProfileType_Guest&apos;' value='0'/>
<EnumValue name='&apos;ProfileType_Normal&apos;' value='1'/>
<EnumValue name='&apos;ProfileType_Test&apos;' value='2'/>
</Enum>
<Enum name='RadarCategory'>
<EnumValue name='&apos;RadarCategory_Stream&apos;' value='0'/>
<EnumValue name='&apos;RadarCategory_Voltage&apos;' value='1'/>
+6
View File
@@ -3463,6 +3463,9 @@ save yourself some time, copy this for undocumented things:
<Function name='GetNumTotalSongsPlayed' return='int' arguments=''>
Returns the total number of songs played with the profile.
</Function>
<Function name='GetPriority' return='int' arguments=''>
Returns the position the profile should have in its category in the list.
</Function>
<Function name='GetSongNumTimesPlayed' return='int' arguments='Song s'>
Returns the number of times song <code>s</code> has been played with the profile.
</Function>
@@ -3511,6 +3514,9 @@ save yourself some time, copy this for undocumented things:
<Function name='GetTotalTrailsWithTopGrade' return='int' arguments='StepsType st, Difficulty d, Grade g'>
Returns the number of trails with the specified StepsType and Difficulty that you've scored a certain Grade <code>g</code> on.
</Function>
<Function name='GetType' return='ProfileType' arguments=''>
Returns the type of the profile. The type of the profile is only used to determine where the profile shows up in the list of profiles, and that problem is already handled by ProfileManager, so if you're reading this, this function only exists so you can make your theme color this profile's list entry based on the type or something like that.
</Function>
<Function name='GetUserTable' return='table' arguments=''>
Returns the user table for this Profile.
</Function>
+9
View File
@@ -1229,6 +1229,15 @@ 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
ChangeToGuest=Change To Guest
ChangeToNormal=Change To Normal
ChangeToTest=Change To Test
MoveUp=Move Up
MoveDown=Move Down
[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
+257 -6
View File
@@ -26,10 +26,14 @@
#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. */
const RString EDITABLE_INI = "Editable.ini";
/** @brief A tiny file containing the type and list priority. */
const RString TYPE_INI = "Type.ini";
/** @brief The filename containing the signature for STATS_XML's signature. */
const RString DONT_SHARE_SIG = "DontShare.sig";
const RString PUBLIC_KEY_FILE = "public.key";
@@ -58,6 +62,15 @@ const float DEFAULT_BIRTH_YEAR= 1995;
#pragma warning (disable : 4706) // assignment within conditional expression
#endif
static const char* ProfileTypeNames[] = {
"Guest",
"Normal",
"Test",
};
XToString(ProfileType);
StringToX(ProfileType);
LuaXType(ProfileType);
int Profile::HighScoresForASong::GetNumTimesPlayed() const
{
@@ -547,7 +560,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 +577,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 +798,198 @@ 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());
}
}
void Profile::swap(Profile& other)
{
// Type is skipped because this is meant to be used only on matching types,
// to move profiles after the priorities have been assigned. -Kyz
// A bit of a misnomer, since it actually works on any type that has its
// own swap function, which includes the standard containers.
#define SWAP_STR_MEMBER(member_name) member_name.swap(other.member_name)
#define SWAP_GENERAL(member_name) std::swap(member_name, other.member_name)
SWAP_GENERAL(m_ListPriority);
SWAP_STR_MEMBER(m_sDisplayName);
SWAP_STR_MEMBER(m_sCharacterID);
SWAP_STR_MEMBER(m_sLastUsedHighScoreName);
SWAP_GENERAL(m_iWeightPounds);
SWAP_GENERAL(m_Voomax);
SWAP_GENERAL(m_BirthYear);
SWAP_GENERAL(m_IgnoreStepCountCalories);
SWAP_GENERAL(m_IsMale);
SWAP_STR_MEMBER(m_sGuid);
SWAP_GENERAL(m_iCurrentCombo);
SWAP_GENERAL(m_iTotalSessions);
SWAP_GENERAL(m_iTotalSessionSeconds);
SWAP_GENERAL(m_iTotalGameplaySeconds);
SWAP_GENERAL(m_fTotalCaloriesBurned);
SWAP_GENERAL(m_GoalType);
SWAP_GENERAL(m_iGoalCalories);
SWAP_GENERAL(m_iGoalSeconds);
SWAP_GENERAL(m_iTotalDancePoints);
SWAP_GENERAL(m_iNumExtraStagesPassed);
SWAP_GENERAL(m_iNumExtraStagesFailed);
SWAP_GENERAL(m_iNumToasties);
SWAP_GENERAL(m_iTotalTapsAndHolds);
SWAP_GENERAL(m_iTotalJumps);
SWAP_GENERAL(m_iTotalHolds);
SWAP_GENERAL(m_iTotalRolls);
SWAP_GENERAL(m_iTotalMines);
SWAP_GENERAL(m_iTotalHands);
SWAP_GENERAL(m_iTotalLifts);
SWAP_GENERAL(m_bNewProfile);
SWAP_STR_MEMBER(m_UnlockedEntryIDs);
SWAP_STR_MEMBER(m_sLastPlayedMachineGuid);
SWAP_GENERAL(m_LastPlayedDate);
SWAP_GENERAL(m_iNumSongsPlayedByPlayMode);
SWAP_STR_MEMBER(m_iNumSongsPlayedByStyle);
SWAP_GENERAL(m_iNumSongsPlayedByDifficulty);
SWAP_GENERAL(m_iNumSongsPlayedByMeter);
SWAP_GENERAL(m_iNumTotalSongsPlayed);
SWAP_GENERAL(m_iNumStagesPassedByPlayMode);
SWAP_GENERAL(m_iNumStagesPassedByGrade);
SWAP_GENERAL(m_UserTable);
SWAP_STR_MEMBER(m_SongHighScores);
SWAP_STR_MEMBER(m_CourseHighScores);
SWAP_GENERAL(m_CategoryHighScores);
SWAP_STR_MEMBER(m_vScreenshots);
SWAP_STR_MEMBER(m_mapDayToCaloriesBurned);
#undef SWAP_STR_MEMBER
#undef SWAP_GENERAL
}
// Category high scores
void Profile::AddCategoryHighScore( StepsType st, RankingCategory rc, HighScore hs, int &iIndexOut )
{
@@ -862,6 +1067,7 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature
InitAll();
LoadTypeFromDir(sDir);
// Not critical if this fails
LoadEditableDataFromDir( sDir );
@@ -949,6 +1155,34 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature
return ProfileLoadResult_Success;
}
void Profile::LoadTypeFromDir(RString dir)
{
m_Type= ProfileType_Normal;
m_ListPriority= 0;
RString fn= dir + TYPE_INI;
if(FILEMAN->DoesFileExist(fn))
{
IniFile ini;
if(ini.ReadFile(fn))
{
XNode const* data= ini.GetChild("ListPosition");
if(data != NULL)
{
RString type_str;
if(data->GetAttrValue("Type", type_str))
{
m_Type= StringToProfileType(type_str);
if(m_Type >= NUM_ProfileType)
{
m_Type= ProfileType_Normal;
}
}
data->GetAttrValue("Priority", m_ListPriority);
}
}
}
}
ProfileLoadResult Profile::LoadStatsXmlFromNode( const XNode *xml, bool bIgnoreEditable )
{
/* The placeholder stats.xml file has an <html> tag. Don't load it,
@@ -999,6 +1233,7 @@ bool Profile::SaveAllToDir( RString sDir, bool bSignData ) const
m_sLastPlayedMachineGuid = PROFILEMAN->GetMachineProfile()->m_sGuid;
m_LastPlayedDate = DateTime::GetNowDate();
SaveTypeToDir(sDir);
// Save editable.ini
SaveEditableDataToDir( sDir );
@@ -1107,6 +1342,14 @@ bool Profile::SaveStatsXmlToDir( RString sDir, bool bSignData ) const
return true;
}
void Profile::SaveTypeToDir(RString dir) const
{
IniFile ini;
ini.SetValue("ListPosition", "Type", ProfileTypeToString(m_Type));
ini.SetValue("ListPosition", "Priority", m_ListPriority);
ini.WriteFile(dir + TYPE_INI);
}
void Profile::SaveEditableDataToDir( RString sDir ) const
{
IniFile ini;
@@ -1634,8 +1877,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 +1959,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
@@ -2149,6 +2396,8 @@ public:
p->AddScreenshot(screenshot);
return 0;
}
DEFINE_METHOD(GetType, m_Type);
DEFINE_METHOD(GetPriority, m_ListPriority);
static int GetDisplayName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sDisplayName ); return 1; }
static int SetDisplayName( T* p, lua_State *L )
@@ -2371,6 +2620,8 @@ public:
LunaProfile()
{
ADD_METHOD( AddScreenshot );
ADD_METHOD( GetType );
ADD_METHOD( GetPriority );
ADD_METHOD( GetDisplayName );
ADD_METHOD( SetDisplayName );
ADD_METHOD( GetLastUsedHighScoreName );
+31 -1
View File
@@ -60,6 +60,18 @@ class Song;
class Steps;
class Course;
struct Game;
// Profile types exist for sorting the list of profiles.
// Guest profiles at the top, test at the bottom.
enum ProfileType
{
ProfileType_Guest,
ProfileType_Normal,
ProfileType_Test,
NUM_ProfileType,
ProfileType_Invalid
};
/**
* @brief Player data that persists between sessions.
*
@@ -71,7 +83,14 @@ public:
* @brief Set up the Profile with default values.
*
* Note: there are probably a lot of variables. */
Profile(): m_sDisplayName(""), m_sCharacterID(""),
// When adding new score related data, add logic for handling it to
// MergeScoresFromOtherProfile. -Kyz
// When adding any new fields, add them to SwapExceptPriority. Anything not
// added to SwapExceptPriority won't be swapped correctly when the user
// changes the list priority of a profile. -Kyz
Profile():
m_Type(ProfileType_Normal), m_ListPriority(0),
m_sDisplayName(""), m_sCharacterID(""),
m_sLastUsedHighScoreName(""), m_iWeightPounds(0),
m_Voomax(0), m_BirthYear(0), m_IgnoreStepCountCalories(false),
m_IsMale(true),
@@ -147,6 +166,10 @@ public:
bool IsMachine() const;
ProfileType m_Type;
// Profiles of the same type and priority are sorted by dir name.
int m_ListPriority;
// Editable data
RString m_sDisplayName;
RString m_sCharacterID;
@@ -277,6 +300,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];
@@ -361,8 +387,11 @@ public:
void InitCalorieData();
void ClearStats();
void swap(Profile& other);
// Loading and saving
ProfileLoadResult LoadAllFromDir( RString sDir, bool bRequireSignature );
void LoadTypeFromDir(RString dir);
void LoadCustomFunction( RString sDir );
bool SaveAllToDir( RString sDir, bool bSignData ) const;
@@ -375,6 +404,7 @@ public:
void LoadScreenshotDataFromNode( const XNode* pNode );
void LoadCalorieDataFromNode( const XNode* pNode );
void SaveTypeToDir(RString dir) const;
void SaveEditableDataToDir( RString sDir ) const;
bool SaveStatsXmlToDir( RString sDir, bool bSignData ) const;
XNode* SaveStatsXmlCreateNode() const;
+233 -25
View File
@@ -27,6 +27,10 @@
ProfileManager* PROFILEMAN = NULL; // global and accessible from anywhere in our program
#define ID_DIGITS 8
#define ID_DIGITS_STR "8"
#define MAX_ID 99999999
static void DefaultLocalProfileIDInit( size_t /*PlayerNumber*/ i, RString &sNameOut, RString &defaultValueOut )
{
sNameOut = ssprintf( "DefaultLocalProfileIDP%d", int(i+1) );
@@ -54,6 +58,11 @@ struct DirAndProfile
{
RString sDir;
Profile profile;
void swap(DirAndProfile& other)
{
sDir.swap(other.sDir);
profile.swap(other.profile);
}
};
static vector<DirAndProfile> g_vLocalProfile;
@@ -399,20 +408,67 @@ void ProfileManager::UnloadAllLocalProfiles()
g_vLocalProfile.clear();
}
static void add_category_to_global_list(vector<DirAndProfile>& cat)
{
g_vLocalProfile.insert(g_vLocalProfile.end(), cat.begin(), cat.end());
}
void ProfileManager::RefreshLocalProfilesFromDisk()
{
UnloadAllLocalProfiles();
vector<RString> vsProfileID;
GetDirListing( USER_PROFILES_DIR + "*", vsProfileID, true, true );
FOREACH_CONST( RString, vsProfileID, p )
vector<RString> profile_ids;
GetDirListing(USER_PROFILES_DIR + "*", profile_ids, true, true);
// Profiles have 3 types:
// 1. Guest profiles:
// Meant for use by guests, always at the top of the list.
// 2. Normal profiles:
// Meant for normal use, listed after guests.
// e. Test profiles:
// Meant for use when testing things, listed last.
// If the user renames a profile directory manually, that should not be a
// problem. -Kyz
map<ProfileType, vector<DirAndProfile> > categorized_profiles;
// The type data for a profile is in its own file so that loading isn't
// slowed down by copying temporary profiles around to make sure the list
// is sorted. The profiles are loaded at the end. -Kyz
FOREACH_CONST(RString, profile_ids, id)
{
g_vLocalProfile.push_back( DirAndProfile() );
DirAndProfile &dap = g_vLocalProfile.back();
dap.sDir = *p + "/";
dap.profile.LoadAllFromDir( dap.sDir, PREFSMAN->m_bSignProfileData );
DirAndProfile derp;
derp.sDir= *id + "/";
derp.profile.LoadTypeFromDir(derp.sDir);
map<ProfileType, vector<DirAndProfile> >::iterator category=
categorized_profiles.find(derp.profile.m_Type);
if(category == categorized_profiles.end())
{
categorized_profiles[derp.profile.m_Type].push_back(derp);
}
else
{
bool inserted= false;
FOREACH(DirAndProfile, category->second, curr)
{
if(curr->profile.m_ListPriority > derp.profile.m_ListPriority)
{
category->second.insert(curr, derp);
inserted= true;
break;
}
}
if(!inserted)
{
category->second.push_back(derp);
}
}
}
}
add_category_to_global_list(categorized_profiles[ProfileType_Guest]);
add_category_to_global_list(categorized_profiles[ProfileType_Normal]);
add_category_to_global_list(categorized_profiles[ProfileType_Test]);
FOREACH(DirAndProfile, g_vLocalProfile, curr)
{
curr->profile.LoadAllFromDir(curr->sDir, PREFSMAN->m_bSignProfileData);
}
}
const Profile *ProfileManager::GetLocalProfile( const RString &sProfileID ) const
{
@@ -433,14 +489,44 @@ bool ProfileManager::CreateLocalProfile( RString sName, RString &sProfileIDOut )
// Find a directory directory name that's a number greater than all
// existing numbers. This preserves the "order by create date".
int iMaxProfileNumber = -1;
vector<RString> vs;
GetLocalProfileIDs( vs );
FOREACH_CONST( RString, vs, s )
iMaxProfileNumber = StringToInt( *s );
// Profile IDs are actually the directory names, so they can be any string,
// and we have to handle the case where the user renames one.
// Since the user can rename them, they might have any number, wrapping our
// counter or setting it to a ridiculous value. That case must also be
// handled. -Kyz
int max_profile_number= -1;
int first_free_number= 0;
vector<RString> profile_ids;
GetLocalProfileIDs(profile_ids);
FOREACH_CONST(RString, profile_ids, id)
{
int tmp= 0;
if((*id) >> tmp)
{
// The profile ids are already in order, so we don't have to handle the
// case where 5 is encountered before 3.
if(tmp == first_free_number)
{
++first_free_number;
}
max_profile_number= max(tmp, max_profile_number);
}
}
int iProfileNumber = iMaxProfileNumber + 1;
RString sProfileID = ssprintf( "%08d", iProfileNumber );
int profile_number = max_profile_number + 1;
// Prevent profiles from going over the 8 digit limit.
if(profile_number > MAX_ID || profile_number < 0)
{
profile_number= first_free_number;
}
ASSERT_M(profile_number >= 0 && profile_number <= MAX_ID,
"Too many profiles, cannot assign ID to new profile.");
RString profile_id = ssprintf( "%0" ID_DIGITS_STR "d", profile_number );
// make sure this id doesn't already exist
ASSERT_M(GetLocalProfile(profile_id) == NULL,
ssprintf("creating profile with ID \"%s\" that already exists",
profile_id.c_str()));
// Create the new profile.
Profile *pProfile = new Profile;
@@ -448,7 +534,7 @@ bool ProfileManager::CreateLocalProfile( RString sName, RString &sProfileIDOut )
pProfile->m_sCharacterID = CHARMAN->GetRandomCharacter()->m_sCharacterID;
// Save it to disk.
RString sProfileDir = LocalProfileIDToDir( sProfileID );
RString sProfileDir = LocalProfileIDToDir(profile_id);
if( !pProfile->SaveAllToDir(sProfileDir, PREFSMAN->m_bSignProfileData) )
{
delete pProfile;
@@ -456,12 +542,37 @@ bool ProfileManager::CreateLocalProfile( RString sName, RString &sProfileIDOut )
return false;
}
AddLocalProfileByID( pProfile, sProfileID );
AddLocalProfileByID(pProfile, profile_id);
sProfileIDOut = sProfileID;
sProfileIDOut = profile_id;
return true;
}
static void InsertProfileIntoList(DirAndProfile& derp)
{
bool inserted= false;
derp.profile.m_ListPriority= 0;
FOREACH(DirAndProfile, g_vLocalProfile, curr)
{
if(curr->profile.m_Type > derp.profile.m_Type)
{
derp.profile.SaveTypeToDir(derp.sDir);
g_vLocalProfile.insert(curr, derp);
inserted= true;
break;
}
else if(curr->profile.m_Type == derp.profile.m_Type)
{
++derp.profile.m_ListPriority;
}
}
if(!inserted)
{
derp.profile.SaveTypeToDir(derp.sDir);
g_vLocalProfile.push_back(derp);
}
}
void ProfileManager::AddLocalProfileByID( Profile *pProfile, RString sProfileID )
{
// make sure this id doesn't already exist
@@ -469,11 +580,10 @@ void ProfileManager::AddLocalProfileByID( Profile *pProfile, RString sProfileID
ssprintf("creating \"%s\" \"%s\" that already exists",
pProfile->m_sDisplayName.c_str(), sProfileID.c_str()) );
// insert
g_vLocalProfile.push_back( DirAndProfile() );
DirAndProfile &dap = g_vLocalProfile.back();
dap.sDir = LocalProfileIDToDir( sProfileID );
dap.profile = *pProfile;
DirAndProfile derp;
derp.sDir= LocalProfileIDToDir(sProfileID);
derp.profile= *pProfile;
InsertProfileIntoList(derp);
}
bool ProfileManager::RenameLocalProfile( RString sProfileID, RString sNewName )
@@ -620,6 +730,81 @@ 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);
}
void ProfileManager::ChangeProfileType(int index, ProfileType new_type)
{
if(index < 0 || static_cast<size_t>(index) >= g_vLocalProfile.size())
{ return; }
if(new_type == g_vLocalProfile[index].profile.m_Type)
{ return; }
DirAndProfile derp= g_vLocalProfile[index];
g_vLocalProfile.erase(g_vLocalProfile.begin() + index);
derp.profile.m_Type= new_type;
InsertProfileIntoList(derp);
}
void ProfileManager::MoveProfilePriority(int index, bool up)
{
if(index < 0 || static_cast<size_t>(index) >= g_vLocalProfile.size())
{ return; }
// Changing the priority is complicated a bit because the profiles might
// all have the same priority. So this function has to assign priorities
// to all the profiles of the same type.
// bools are numbers, true evaluatues to 1.
int swindex= index + ((up * -2) + 1);
ProfileType type= g_vLocalProfile[index].profile.m_Type;
int priority= 0;
for(size_t i= 0; i < g_vLocalProfile.size(); ++i)
{
DirAndProfile* curr= &g_vLocalProfile[i];
if(curr->profile.m_Type == type)
{
if(curr->profile.m_ListPriority != priority)
{
curr->profile.m_ListPriority= priority;
if(i != index && i != swindex)
{
curr->profile.SaveTypeToDir(curr->sDir);
}
}
++priority;
}
else if(curr->profile.m_Type > type)
{
break;
}
}
// Only swap if both indices are valid and the types match.
if(swindex >= 0 && static_cast<size_t>(swindex) < g_vLocalProfile.size() &&
g_vLocalProfile[swindex].profile.m_Type ==
g_vLocalProfile[index].profile.m_Type)
{
g_vLocalProfile[index].swap(g_vLocalProfile[swindex]);
}
}
//
// General
//
@@ -839,8 +1024,31 @@ public:
lua_pushnil(L);
return 1;
}
static int GetLocalProfileFromIndex( T* p, lua_State *L ) { Profile *pProfile = p->GetLocalProfileFromIndex(IArg(1)); ASSERT(pProfile != NULL); pProfile->PushSelf(L); return 1; }
static int GetLocalProfileIDFromIndex( T* p, lua_State *L ) { lua_pushstring(L, p->GetLocalProfileIDFromIndex(IArg(1)) ); return 1; }
static int GetLocalProfileFromIndex( T* p, lua_State *L )
{
int index= IArg(1);
if(index >= p->GetNumLocalProfiles())
{
luaL_error(L, "Profile index %d out of range.", index);
}
Profile *pProfile = p->GetLocalProfileFromIndex(index);
if(pProfile == NULL)
{
luaL_error(L, "No profile at index %d.", index);
}
pProfile->PushSelf(L);
return 1;
}
static int GetLocalProfileIDFromIndex( T* p, lua_State *L )
{
int index= IArg(1);
if(index >= p->GetNumLocalProfiles())
{
luaL_error(L, "Profile index %d out of range.", index);
}
lua_pushstring(L, p->GetLocalProfileIDFromIndex(index) );
return 1;
}
static int GetLocalProfileIndexFromID( T* p, lua_State *L ) { lua_pushnumber(L, p->GetLocalProfileIndexFromID(SArg(1)) ); return 1; }
static int GetNumLocalProfiles( T* p, lua_State *L ) { lua_pushnumber(L, p->GetNumLocalProfiles() ); return 1; }
static int GetProfileDir( T* p, lua_State *L ) { lua_pushstring(L, p->GetProfileDir(Enum::Check<ProfileSlot>(L, 1)) ); return 1; }
+6 -1
View File
@@ -6,8 +6,8 @@
#include "Difficulty.h"
#include "Preference.h"
#include "Grade.h"
#include "Profile.h"
class Profile;
class Song;
class Steps;
class Style;
@@ -52,6 +52,11 @@ 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);
void ChangeProfileType(int index, ProfileType new_type);
void MoveProfilePriority(int index, bool up);
// 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
/*
+66
View File
@@ -30,6 +30,15 @@ enum ProfileAction
ProfileAction_Rename,
ProfileAction_Delete,
ProfileAction_Clear,
ProfileAction_MergeToMachine,
ProfileAction_MergeToMachineSkipTotal,
ProfileAction_MergeToP1,
ProfileAction_MergeToP2,
ProfileAction_ChangeToGuest,
ProfileAction_ChangeToNormal,
ProfileAction_ChangeToTest,
ProfileAction_MoveUp,
ProfileAction_MoveDown,
NUM_ProfileAction
};
static const char *ProfileActionNames[] = {
@@ -39,6 +48,15 @@ static const char *ProfileActionNames[] = {
"Rename",
"Delete",
"Clear",
"MergeToMachine",
"MergeToMachineSkipTotal",
"MergeToP1",
"MergeToP2",
"ChangeToGuest",
"ChangeToNormal",
"ChangeToTest",
"MoveUp",
"MoveDown",
};
XToString( ProfileAction );
XToLocalizedString( ProfileAction );
@@ -309,6 +327,45 @@ 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;
case ProfileAction_ChangeToGuest:
PROFILEMAN->ChangeProfileType(GetLocalProfileIndexWithFocus(),
ProfileType_Guest);
SCREENMAN->SetNewScreen(this->m_sName); // reload
break;
case ProfileAction_ChangeToNormal:
PROFILEMAN->ChangeProfileType(GetLocalProfileIndexWithFocus(),
ProfileType_Normal);
SCREENMAN->SetNewScreen(this->m_sName); // reload
break;
case ProfileAction_ChangeToTest:
PROFILEMAN->ChangeProfileType(GetLocalProfileIndexWithFocus(),
ProfileType_Test);
SCREENMAN->SetNewScreen(this->m_sName); // reload
break;
case ProfileAction_MoveUp:
PROFILEMAN->MoveProfilePriority(GetLocalProfileIndexWithFocus(), true);
SCREENMAN->SetNewScreen(this->m_sName); // reload
break;
case ProfileAction_MoveDown:
PROFILEMAN->MoveProfilePriority(GetLocalProfileIndexWithFocus(), false);
SCREENMAN->SetNewScreen(this->m_sName); // reload
break;
}
}
}
@@ -383,6 +440,15 @@ 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 );
ADD_ACTION( ProfileAction_ChangeToGuest );
ADD_ACTION( ProfileAction_ChangeToNormal );
ADD_ACTION( ProfileAction_ChangeToTest );
ADD_ACTION( ProfileAction_MoveUp );
ADD_ACTION( ProfileAction_MoveDown );
}
int iWidth, iX, iY;