Files
itgmania212121/src/ProfileManager.cpp
T
2019-06-22 12:35:38 -07:00

1188 lines
38 KiB
C++

#include "global.h"
#include "ProfileManager.h"
#include "Profile.h"
#include "RageUtil.h"
#include "PrefsManager.h"
#include "RageLog.h"
#include "RageFile.h"
#include "RageFileManager.h"
#include "GameConstantsAndTypes.h"
#include "SongManager.h"
#include "GameState.h"
#include "Song.h"
#include "Steps.h"
#include "Course.h"
#include "GameManager.h"
#include "ProductInfo.h"
#include "RageUtil.h"
#include "ThemeManager.h"
#include "MemoryCardManager.h"
#include "XmlFile.h"
#include "StepsUtil.h"
#include "Style.h"
#include "HighScore.h"
#include "Character.h"
#include "CharacterManager.h"
ProfileManager* PROFILEMAN = nullptr; // 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) );
defaultValueOut = "";
}
Preference<bool> ProfileManager::m_bProfileStepEdits( "ProfileStepEdits", true );
Preference<bool> ProfileManager::m_bProfileCourseEdits( "ProfileCourseEdits", true );
Preference1D<RString> ProfileManager::m_sDefaultLocalProfileID( DefaultLocalProfileIDInit, NUM_PLAYERS );
const RString NEW_MEM_CARD_NAME = "";
const RString USER_PROFILES_DIR = "/Save/LocalProfiles/";
const RString MACHINE_PROFILE_DIR = "/Save/MachineProfile/";
const RString LAST_GOOD_SUBDIR = "LastGood/";
// Directories to search for a profile if m_sMemoryCardProfileSubdir doesn't
// exist, separated by ";":
static Preference<RString> g_sMemoryCardProfileImportSubdirs( "MemoryCardProfileImportSubdirs", "" );
static RString LocalProfileIDToDir( const RString &sProfileID ) { return USER_PROFILES_DIR + sProfileID + "/"; }
static RString LocalProfileDirToID( const RString &sDir ) { return Basename( sDir ); }
struct DirAndProfile
{
RString sDir;
Profile profile;
void swap(DirAndProfile& other)
{
sDir.swap(other.sDir);
profile.swap(other.profile);
}
};
static vector<DirAndProfile> g_vLocalProfile;
static ThemeMetric<bool> FIXED_PROFILES ( "ProfileManager", "FixedProfiles" );
static ThemeMetric<int> NUM_FIXED_PROFILES ( "ProfileManager", "NumFixedProfiles" );
#define FIXED_PROFILE_CHARACTER_ID( i ) THEME->GetMetric( "ProfileManager", ssprintf("FixedProfileCharacterID%d",int(i+1)) )
ProfileManager::ProfileManager()
:m_stats_prefix("")
{
m_pMachineProfile = new Profile;
FOREACH_PlayerNumber(pn)
m_pMemoryCardProfile[pn] = new Profile;
// Register with Lua.
{
Lua *L = LUA->Get();
lua_pushstring( L, "PROFILEMAN" );
this->PushSelf( L );
lua_settable( L, LUA_GLOBALSINDEX );
LUA->Release( L );
}
}
ProfileManager::~ProfileManager()
{
// Unregister with Lua.
LUA->UnsetGlobal( "PROFILEMAN" );
SAFE_DELETE( m_pMachineProfile );
FOREACH_PlayerNumber(pn)
SAFE_DELETE( m_pMemoryCardProfile[pn] );
}
void ProfileManager::Init()
{
FOREACH_PlayerNumber( p )
{
m_bWasLoadedFromMemoryCard[p] = false;
m_bLastLoadWasTamperedOrCorrupt[p] = false;
m_bLastLoadWasFromLastGood[p] = false;
m_bNeedToBackUpLastLoad[p] = false;
m_bNewProfile[p] = false;
}
LoadMachineProfile();
RefreshLocalProfilesFromDisk();
if( FIXED_PROFILES )
{
// resize to the fixed number
if( (int)g_vLocalProfile.size() > NUM_FIXED_PROFILES )
g_vLocalProfile.erase( g_vLocalProfile.begin()+NUM_FIXED_PROFILES, g_vLocalProfile.end() );
for( int i=g_vLocalProfile.size(); i<NUM_FIXED_PROFILES; i++ )
{
RString sCharacterID = FIXED_PROFILE_CHARACTER_ID( i );
Character *pCharacter = CHARMAN->GetCharacterFromID( sCharacterID );
ASSERT_M( pCharacter != nullptr, sCharacterID );
RString sProfileID;
bool b = CreateLocalProfile( pCharacter->GetDisplayName(), sProfileID );
ASSERT( b );
Profile* pProfile = GetLocalProfile( sProfileID );
ASSERT_M( pProfile != nullptr, sProfileID );
pProfile->m_sCharacterID = sCharacterID;
SaveLocalProfile( sProfileID );
}
ASSERT( (int)g_vLocalProfile.size() == NUM_FIXED_PROFILES );
}
}
bool ProfileManager::FixedProfiles() const
{
return FIXED_PROFILES;
}
ProfileLoadResult ProfileManager::LoadProfile( PlayerNumber pn, RString sProfileDir, bool bIsMemCard )
{
LOG->Trace( "LoadingProfile P%d, %s, %d", pn+1, sProfileDir.c_str(), bIsMemCard );
ASSERT( !sProfileDir.empty() );
ASSERT( sProfileDir.Right(1) == "/" );
m_sProfileDir[pn] = sProfileDir;
m_bWasLoadedFromMemoryCard[pn] = bIsMemCard;
m_bLastLoadWasFromLastGood[pn] = false;
m_bNeedToBackUpLastLoad[pn] = false;
// Try to load the original, non-backup data.
ProfileLoadResult lr = GetProfile(pn)->LoadAllFromDir( m_sProfileDir[pn], PREFSMAN->m_bSignProfileData );
RString sBackupDir = m_sProfileDir[pn] + LAST_GOOD_SUBDIR;
if( lr == ProfileLoadResult_Success )
{
/* Next time the profile is written, move this good profile into LastGood. */
m_bNeedToBackUpLastLoad[pn] = true;
}
m_bLastLoadWasTamperedOrCorrupt[pn] = lr == ProfileLoadResult_FailedTampered;
//
// Try to load from the backup if the original data fails to load
//
if( lr == ProfileLoadResult_FailedTampered )
{
lr = GetProfile(pn)->LoadAllFromDir( sBackupDir, PREFSMAN->m_bSignProfileData );
m_bLastLoadWasFromLastGood[pn] = lr == ProfileLoadResult_Success;
/* If the LastGood profile doesn't exist at all, and the actual profile was failed_tampered,
* then the error should be failed_tampered and not failed_no_profile. */
if( lr == ProfileLoadResult_FailedNoProfile )
{
LOG->Trace( "Profile was corrupt and LastGood for %s doesn't exist; error is ProfileLoadResult_FailedTampered",
sProfileDir.c_str() );
lr = ProfileLoadResult_FailedTampered;
}
}
if(lr == ProfileLoadResult_Success)
{
Profile* prof= GetProfile(pn);
if(prof->m_sDisplayName.empty())
{
prof->m_sDisplayName= PlayerNumberToLocalizedString(pn);
}
prof->LoadCustomFunction(sProfileDir, pn);
prof->LoadSongsFromDir(sProfileDir, ProfileSlot(pn));
}
LOG->Trace( "Done loading profile - result %d", lr );
return lr;
}
bool ProfileManager::LoadLocalProfileFromMachine( PlayerNumber pn )
{
RString sProfileID = m_sDefaultLocalProfileID[pn];
if( sProfileID.empty() )
{
m_sProfileDir[pn] = "";
return false;
}
m_sProfileDir[pn] = LocalProfileIDToDir( sProfileID );
m_bWasLoadedFromMemoryCard[pn] = false;
m_bLastLoadWasFromLastGood[pn] = false;
if( GetLocalProfile(sProfileID) == nullptr )
{
m_sProfileDir[pn] = "";
return false;
}
GetProfile(pn)->LoadCustomFunction(m_sProfileDir[pn], pn);
return true;
}
void ProfileManager::GetMemoryCardProfileDirectoriesToTry( vector<RString> &asDirsToTry )
{
/* Try to load the preferred profile. */
asDirsToTry.push_back( PREFSMAN->m_sMemoryCardProfileSubdir.Get() );
/* If that failed, try loading from all fallback directories. */
split( g_sMemoryCardProfileImportSubdirs, ";", asDirsToTry, true );
}
bool ProfileManager::LoadProfileFromMemoryCard( PlayerNumber pn, bool bLoadEdits )
{
UnloadProfile( pn );
// mount slot
if( MEMCARDMAN->GetCardState(pn) != MemoryCardState_Ready )
return false;
vector<RString> asDirsToTry;
GetMemoryCardProfileDirectoriesToTry( asDirsToTry );
m_bNewProfile[pn] = true;
for( unsigned i = 0; i < asDirsToTry.size(); ++i )
{
const RString &sSubdir = asDirsToTry[i];
RString sDir = MEM_CARD_MOUNT_POINT[pn] + sSubdir + "/";
/* If the load fails with ProfileLoadResult_FailedNoProfile, keep searching. However,
* if it fails with failed_tampered, data existed but couldn't be loaded;
* we don't want to mess with it, since it's confusing and may wipe out
* recoverable backup data. The only time we really want to import data
* is on the very first use, when the new profile doesn't exist at all,
* but we also want to import scores in the case where the player created
* a directory for edits before playing, so keep searching if the directory
* exists with exists with no scores. */
ProfileLoadResult res = LoadProfile( pn, sDir, true );
if( res == ProfileLoadResult_Success )
{
m_bNewProfile[pn] = false;
/* If importing, store the directory we imported from, for display purposes. */
if( i > 0 )
m_sProfileDirImportedFrom[pn] = asDirsToTry[i];
break;
}
if( res == ProfileLoadResult_FailedTampered )
{
m_bNewProfile[pn] = false;
break;
}
}
/* If we imported a profile fallback directory, change the memory card
* directory back to the preferred directory: never write over imported
* scores. */
m_sProfileDir[pn] = MEM_CARD_MOUNT_POINT[pn] + (RString)PREFSMAN->m_sMemoryCardProfileSubdir + "/";
/* Load edits from all fallback directories, newest first. */
if( bLoadEdits )
{
for( unsigned i = 0; i < asDirsToTry.size(); ++i )
{
const RString &sSubdir = asDirsToTry[i];
RString sDir = MEM_CARD_MOUNT_POINT[pn] + sSubdir + "/";
if( m_bProfileStepEdits )
SONGMAN->LoadStepEditsFromProfileDir( sDir, (ProfileSlot) pn );
if( m_bProfileCourseEdits )
SONGMAN->LoadCourseEditsFromProfileDir( sDir, (ProfileSlot) pn );
}
}
return true; // If a card is inserted, we want to use the memory card to save - even if the Profile load failed.
}
bool ProfileManager::LoadFirstAvailableProfile( PlayerNumber pn, bool bLoadEdits )
{
if( LoadProfileFromMemoryCard(pn, bLoadEdits) )
return true;
if( LoadLocalProfileFromMachine(pn) )
return true;
return false;
}
bool ProfileManager::FastLoadProfileNameFromMemoryCard( RString sRootDir, RString &sName ) const
{
vector<RString> asDirsToTry;
GetMemoryCardProfileDirectoriesToTry( asDirsToTry );
for( unsigned i = 0; i < asDirsToTry.size(); ++i )
{
const RString &sSubdir = asDirsToTry[i];
RString sDir = sRootDir + sSubdir + "/";
Profile profile;
ProfileLoadResult res = profile.LoadEditableDataFromDir( sDir );
if( res == ProfileLoadResult_Success )
{
sName = profile.GetDisplayNameOrHighScoreName();
return true;
}
else if( res != ProfileLoadResult_FailedNoProfile )
break;
}
return false;
}
bool ProfileManager::SaveProfile( PlayerNumber pn ) const
{
if( m_sProfileDir[pn].empty() )
return false;
/*
* If the profile we're writing was loaded from the primary (non-backup)
* data, then we've validated it and know it's good. Before writing our
* new data, move the old, good data to the backup. (Only do this once;
* if we save the profile more than once, we haven't re-validated the
* newly written data.)
*/
if( m_bNeedToBackUpLastLoad[pn] )
{
m_bNeedToBackUpLastLoad[pn] = false;
RString sBackupDir = m_sProfileDir[pn] + LAST_GOOD_SUBDIR;
Profile::MoveBackupToDir( m_sProfileDir[pn], sBackupDir );
}
bool b = GetProfile(pn)->SaveAllToDir( m_sProfileDir[pn], PREFSMAN->m_bSignProfileData );
return b;
}
bool ProfileManager::SaveLocalProfile( RString sProfileID )
{
const Profile *pProfile = GetLocalProfile( sProfileID );
ASSERT( pProfile != nullptr );
RString sDir = LocalProfileIDToDir( sProfileID );
bool b = pProfile->SaveAllToDir( sDir, PREFSMAN->m_bSignProfileData );
return b;
}
void ProfileManager::UnloadProfile( PlayerNumber pn )
{
if( m_sProfileDir[pn].empty() )
{
// Don't bother unloading a profile that wasn't loaded in the first place.
// Saves us an expensive and pointless trip through all the songs.
return;
}
m_sProfileDir[pn] = "";
m_sProfileDirImportedFrom[pn] = "";
m_bWasLoadedFromMemoryCard[pn] = false;
m_bLastLoadWasTamperedOrCorrupt[pn] = false;
m_bLastLoadWasFromLastGood[pn] = false;
m_bNeedToBackUpLastLoad[pn] = false;
m_pMemoryCardProfile[pn]->InitAll();
SONGMAN->FreeAllLoadedFromProfile( (ProfileSlot) pn );
}
const Profile* ProfileManager::GetProfile( PlayerNumber pn ) const
{
ASSERT( pn >= 0 && pn < NUM_PLAYERS );
if( m_sProfileDir[pn].empty() )
{
// return an empty profile
return m_pMemoryCardProfile[pn];
}
else if( ProfileWasLoadedFromMemoryCard(pn) )
{
return m_pMemoryCardProfile[pn];
}
else
{
RString sProfileID = LocalProfileDirToID( m_sProfileDir[pn] );
return GetLocalProfile( sProfileID );
}
}
RString ProfileManager::GetPlayerName( PlayerNumber pn ) const
{
const Profile *prof = GetProfile( pn );
return prof ? prof->GetDisplayNameOrHighScoreName() : RString();
}
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> 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
for (RString const &id : profile_ids)
{
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;
for (auto curr = g_vLocalProfile.begin(); curr != g_vLocalProfile.end(); ++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]);
for (DirAndProfile &curr : g_vLocalProfile)
{
curr.profile.LoadAllFromDir(curr.sDir, PREFSMAN->m_bSignProfileData);
}
}
const Profile *ProfileManager::GetLocalProfile( const RString &sProfileID ) const
{
RString sDir = LocalProfileIDToDir( sProfileID );
for (DirAndProfile const &dap : g_vLocalProfile)
{
const RString &sOther = dap.sDir;
if( sOther == sDir )
return &dap.profile;
}
return nullptr;
}
bool ProfileManager::CreateLocalProfile( RString sName, RString &sProfileIDOut )
{
ASSERT( !sName.empty() );
// Find a directory directory name that's a number greater than all
// existing numbers. This preserves the "order by create date".
// 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);
for (std::vector<RString>::const_iterator id = profile_ids.begin(); id != profile_ids.end(); ++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 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) == nullptr,
ssprintf("creating profile with ID \"%s\" that already exists",
profile_id.c_str()));
// Create the new profile.
Profile *pProfile = new Profile;
pProfile->m_sDisplayName = sName;
pProfile->m_sCharacterID = CHARMAN->GetRandomCharacter()->m_sCharacterID;
// Save it to disk.
RString sProfileDir = LocalProfileIDToDir(profile_id);
if( !pProfile->SaveAllToDir(sProfileDir, PREFSMAN->m_bSignProfileData) )
{
delete pProfile;
sProfileIDOut = "";
return false;
}
AddLocalProfileByID(pProfile, profile_id);
sProfileIDOut = profile_id;
return true;
}
static void InsertProfileIntoList(DirAndProfile& derp)
{
bool inserted= false;
derp.profile.m_ListPriority= 0;
for (auto curr = g_vLocalProfile.begin(); curr != g_vLocalProfile.end(); ++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
ASSERT_M( GetLocalProfile(sProfileID) == nullptr,
ssprintf("creating \"%s\" \"%s\" that already exists",
pProfile->m_sDisplayName.c_str(), sProfileID.c_str()) );
DirAndProfile derp;
derp.sDir= LocalProfileIDToDir(sProfileID);
derp.profile= *pProfile;
InsertProfileIntoList(derp);
}
bool ProfileManager::RenameLocalProfile( RString sProfileID, RString sNewName )
{
ASSERT( !sProfileID.empty() );
Profile *pProfile = ProfileManager::GetLocalProfile( sProfileID );
ASSERT( pProfile != nullptr );
pProfile->m_sDisplayName = sNewName;
RString sProfileDir = LocalProfileIDToDir( sProfileID );
return pProfile->SaveAllToDir( sProfileDir, PREFSMAN->m_bSignProfileData );
}
bool ProfileManager::DeleteLocalProfile( RString sProfileID )
{
Profile *pProfile = ProfileManager::GetLocalProfile( sProfileID );
ASSERT( pProfile != nullptr );
RString sProfileDir = LocalProfileIDToDir( sProfileID );
// flush directory cache in an attempt to get this working
FILEMAN->FlushDirCache( sProfileDir );
for (vector<DirAndProfile>::iterator i = g_vLocalProfile.begin(); i != g_vLocalProfile.end(); ++i)
{
if( i->sDir == sProfileDir )
{
if( DeleteRecursive(sProfileDir) )
{
g_vLocalProfile.erase( i );
// Delete all references to this profileID
for (Preference<RString> *j : m_sDefaultLocalProfileID.m_v)
{
if( j->Get() == sProfileID )
j->Set( "" );
}
return true;
}
else
{
LOG->Warn("[ProfileManager::DeleteLocalProfile] DeleteRecursive(%s) failed",
sProfileID.c_str() );
return false;
}
}
}
LOG->Warn( "DeleteLocalProfile: ProfileID '%s' doesn't exist", sProfileID.c_str() );
return false;
}
void ProfileManager::SaveMachineProfile() const
{
// If the machine name has changed, make sure we use the new name.
// It's important that this name be applied before the Player profiles
// are saved, so that the Player's profiles show the right machine name.
const_cast<ProfileManager *> (this)->m_pMachineProfile->m_sDisplayName = PREFSMAN->m_sMachineName;
m_pMachineProfile->SaveAllToDir( MACHINE_PROFILE_DIR, false ); /* don't sign machine profiles */
}
void ProfileManager::LoadMachineProfile()
{
ProfileLoadResult lr = m_pMachineProfile->LoadAllFromDir(MACHINE_PROFILE_DIR, false);
if( lr == ProfileLoadResult_FailedNoProfile )
{
m_pMachineProfile->InitAll();
m_pMachineProfile->SaveAllToDir( MACHINE_PROFILE_DIR, false ); /* don't sign machine profiles */
}
// If the machine name has changed, make sure we use the new name
m_pMachineProfile->m_sDisplayName = PREFSMAN->m_sMachineName;
LoadMachineProfileEdits();
}
void ProfileManager::LoadMachineProfileEdits()
{
SONGMAN->FreeAllLoadedFromProfile( ProfileSlot_Machine );
SONGMAN->LoadStepEditsFromProfileDir( MACHINE_PROFILE_DIR, ProfileSlot_Machine );
SONGMAN->LoadCourseEditsFromProfileDir( MACHINE_PROFILE_DIR, ProfileSlot_Machine );
}
bool ProfileManager::ProfileWasLoadedFromMemoryCard( PlayerNumber pn ) const
{
return !m_sProfileDir[pn].empty() && m_bWasLoadedFromMemoryCard[pn];
}
bool ProfileManager::ProfileFromMemoryCardIsNew( PlayerNumber pn ) const
{
return GetProfile(pn) && m_bWasLoadedFromMemoryCard[pn] && m_bNewProfile[pn];
}
bool ProfileManager::LastLoadWasTamperedOrCorrupt( PlayerNumber pn ) const
{
return !m_sProfileDir[pn].empty() && m_bLastLoadWasTamperedOrCorrupt[pn];
}
bool ProfileManager::LastLoadWasFromLastGood( PlayerNumber pn ) const
{
return !m_sProfileDir[pn].empty() && m_bLastLoadWasFromLastGood[pn];
}
const RString& ProfileManager::GetProfileDir( ProfileSlot slot ) const
{
switch( slot )
{
case ProfileSlot_Player1:
case ProfileSlot_Player2:
return m_sProfileDir[slot];
case ProfileSlot_Machine:
return MACHINE_PROFILE_DIR;
default:
FAIL_M("Invalid profile slot chosen: unable to get the directory!");
}
}
RString ProfileManager::GetProfileDirImportedFrom( ProfileSlot slot ) const
{
switch( slot )
{
case ProfileSlot_Player1:
case ProfileSlot_Player2:
return m_sProfileDirImportedFrom[slot];
case ProfileSlot_Machine:
return RString();
default:
FAIL_M("Invalid profile slot chosen: unable to get the directory!");
}
}
const Profile* ProfileManager::GetProfile( ProfileSlot slot ) const
{
switch( slot )
{
case ProfileSlot_Player1:
case ProfileSlot_Player2:
return GetProfile( (PlayerNumber)slot );
case ProfileSlot_Machine:
return m_pMachineProfile;
default:
FAIL_M("Invalid profile slot chosen: unable to get the profile!");
}
}
void ProfileManager::MergeLocalProfiles(RString const& from_id, RString const& to_id)
{
Profile* from= GetLocalProfile(from_id);
Profile* to= GetLocalProfile(to_id);
if(from == nullptr || to == nullptr)
{
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 == nullptr)
{
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 != static_cast<size_t>(index) && i != static_cast<size_t>(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
//
void ProfileManager::IncrementToastiesCount( PlayerNumber pn )
{
if( IsPersistentProfile(pn) )
++GetProfile(pn)->m_iNumToasties;
++GetMachineProfile()->m_iNumToasties;
}
void ProfileManager::AddStepTotals( PlayerNumber pn, int iNumTapsAndHolds, int iNumJumps, int iNumHolds, int iNumRolls, int iNumMines, int iNumHands, int iNumLifts, float fCaloriesBurned )
{
if( IsPersistentProfile(pn) )
GetProfile(pn)->AddStepTotals( iNumTapsAndHolds, iNumJumps, iNumHolds, iNumRolls, iNumMines, iNumHands, iNumLifts, fCaloriesBurned );
GetMachineProfile()->AddStepTotals( iNumTapsAndHolds, iNumJumps, iNumHolds, iNumRolls, iNumMines, iNumHands, iNumLifts, fCaloriesBurned );
}
//
// Song stats
//
int ProfileManager::GetSongNumTimesPlayed( const Song* pSong, ProfileSlot slot ) const
{
return GetProfile(slot)->GetSongNumTimesPlayed( pSong );
}
void ProfileManager::AddStepsScore( const Song* pSong, const Steps* pSteps, PlayerNumber pn, const HighScore &hs_, int &iPersonalIndexOut, int &iMachineIndexOut )
{
HighScore hs = hs_;
hs.SetPercentDP( max(0, hs.GetPercentDP()) ); // bump up negative scores
iPersonalIndexOut = -1;
iMachineIndexOut = -1;
// In event mode, set the score's name immediately to the Profile's last
// used name. If no profile last used name exists, use "EVNT".
if( GAMESTATE->IsEventMode() )
{
Profile* pProfile = GetProfile(pn);
if( pProfile && !pProfile->m_sLastUsedHighScoreName.empty() )
hs.SetName( pProfile->m_sLastUsedHighScoreName );
else
hs.SetName( "EVNT" );
}
else
{
hs.SetName( RANKING_TO_FILL_IN_MARKER[pn] );
}
//
// save high score
//
if( IsPersistentProfile(pn) )
GetProfile(pn)->AddStepsHighScore( pSong, pSteps, hs, iPersonalIndexOut );
// don't save machine scores for a failed song
if( hs.GetPercentDP() >= PREFSMAN->m_fMinPercentageForMachineSongHighScore &&
hs.GetGrade() != Grade_Failed )
{
// don't leave machine high scores for edits loaded from the player's card
if( !pSteps->IsAPlayerEdit() )
GetMachineProfile()->AddStepsHighScore( pSong, pSteps, hs, iMachineIndexOut );
}
/*
// save recent score
if( IsPersistentProfile(pn) )
GetProfile(pn)->SaveStepsRecentScore( pSong, pSteps, hs );
GetMachineProfile()->SaveStepsRecentScore( pSong, pSteps, hs );
*/
}
void ProfileManager::IncrementStepsPlayCount( const Song* pSong, const Steps* pSteps, PlayerNumber pn )
{
if( IsPersistentProfile(pn) )
GetProfile(pn)->IncrementStepsPlayCount( pSong, pSteps );
GetMachineProfile()->IncrementStepsPlayCount( pSong, pSteps );
}
// Course stats
void ProfileManager::AddCourseScore( const Course* pCourse, const Trail* pTrail, PlayerNumber pn, const HighScore &hs_, int &iPersonalIndexOut, int &iMachineIndexOut )
{
HighScore hs = hs_;
hs.SetPercentDP(max( 0, hs.GetPercentDP()) ); // bump up negative scores
iPersonalIndexOut = -1;
iMachineIndexOut = -1;
// In event mode, set the score's name immediately to the Profile's last
// used name. If no profile last used name exists, use "EVNT".
if( GAMESTATE->IsEventMode() )
{
Profile* pProfile = GetProfile(pn);
if( pProfile && !pProfile->m_sLastUsedHighScoreName.empty() )
hs.SetName( pProfile->m_sLastUsedHighScoreName );
else
hs.SetName( "EVNT" );
}
else
{
hs.SetName( RANKING_TO_FILL_IN_MARKER[pn] );
}
// save high score
if( IsPersistentProfile(pn) )
GetProfile(pn)->AddCourseHighScore( pCourse, pTrail, hs, iPersonalIndexOut );
if( hs.GetPercentDP() >= PREFSMAN->m_fMinPercentageForMachineCourseHighScore )
GetMachineProfile()->AddCourseHighScore( pCourse, pTrail, hs, iMachineIndexOut );
/*
// save recent score
if( IsPersistentProfile(pn) )
GetProfile(pn)->SaveCourseRecentScore( pCourse, pTrail, hs );
GetMachineProfile()->SaveCourseRecentScore( pCourse, pTrail, hs );
*/
}
void ProfileManager::IncrementCoursePlayCount( const Course* pCourse, const Trail* pTrail, PlayerNumber pn )
{
if( IsPersistentProfile(pn) )
GetProfile(pn)->IncrementCoursePlayCount( pCourse, pTrail );
GetMachineProfile()->IncrementCoursePlayCount( pCourse, pTrail );
}
// Category stats
void ProfileManager::AddCategoryScore( StepsType st, RankingCategory rc, PlayerNumber pn, const HighScore &hs_, int &iPersonalIndexOut, int &iMachineIndexOut )
{
HighScore hs = hs_;
hs.SetName( RANKING_TO_FILL_IN_MARKER[pn] );
if( IsPersistentProfile(pn) )
GetProfile(pn)->AddCategoryHighScore( st, rc, hs, iPersonalIndexOut );
if( hs.GetPercentDP() > PREFSMAN->m_fMinPercentageForMachineSongHighScore )
GetMachineProfile()->AddCategoryHighScore( st, rc, hs, iMachineIndexOut );
}
void ProfileManager::IncrementCategoryPlayCount( StepsType st, RankingCategory rc, PlayerNumber pn )
{
if( IsPersistentProfile(pn) )
GetProfile(pn)->IncrementCategoryPlayCount( st, rc );
GetMachineProfile()->IncrementCategoryPlayCount( st, rc );
}
bool ProfileManager::IsPersistentProfile( ProfileSlot slot ) const
{
switch( slot )
{
case ProfileSlot_Player1:
case ProfileSlot_Player2:
return GAMESTATE->IsHumanPlayer((PlayerNumber)slot) && !m_sProfileDir[slot].empty();
case ProfileSlot_Machine:
return true;
default:
FAIL_M("Invalid profile slot chosen: unable to get profile info!");
}
}
void ProfileManager::GetLocalProfileIDs( vector<RString> &vsProfileIDsOut ) const
{
vsProfileIDsOut.clear();
for (DirAndProfile const &i : g_vLocalProfile)
{
RString sID = LocalProfileDirToID( i.sDir );
vsProfileIDsOut.push_back( sID );
}
}
void ProfileManager::GetLocalProfileDisplayNames( vector<RString> &vsProfileDisplayNamesOut ) const
{
vsProfileDisplayNamesOut.clear();
for (DirAndProfile const &i : g_vLocalProfile)
vsProfileDisplayNamesOut.push_back( i.profile.m_sDisplayName );
}
int ProfileManager::GetLocalProfileIndexFromID( RString sProfileID ) const
{
RString sDir = LocalProfileIDToDir( sProfileID );
int j = 0;
for (DirAndProfile const &i : g_vLocalProfile)
{
if( i.sDir == sDir )
return j;
++j;
}
return -1;
}
RString ProfileManager::GetLocalProfileIDFromIndex( int iIndex )
{
RString sID = LocalProfileDirToID( g_vLocalProfile[iIndex].sDir );
return sID;
}
Profile *ProfileManager::GetLocalProfileFromIndex( int iIndex )
{
return &g_vLocalProfile[iIndex].profile;
}
int ProfileManager::GetNumLocalProfiles() const
{
return g_vLocalProfile.size();
}
void ProfileManager::SetStatsPrefix(RString const& prefix)
{
m_stats_prefix= prefix;
for(size_t i= 0; i < g_vLocalProfile.size(); ++i)
{
g_vLocalProfile[i].profile.HandleStatsPrefixChange(g_vLocalProfile[i].sDir, PREFSMAN->m_bSignProfileData);
}
FOREACH_PlayerNumber(pn)
{
if(ProfileWasLoadedFromMemoryCard(pn))
{
// This probably runs into a problem if the memory card has been removed. -Kyz
GetProfile(pn)->HandleStatsPrefixChange(m_sProfileDir[pn], PREFSMAN->m_bSignProfileData);
}
}
m_pMachineProfile->HandleStatsPrefixChange(MACHINE_PROFILE_DIR, false);
}
// lua start
#include "LuaBinding.h"
/** @brief Allow Lua to have access to the ProfileManager. */
class LunaProfileManager: public Luna<ProfileManager>
{
public:
static int GetStatsPrefix(T* p, lua_State* L)
{
lua_pushstring(L, p->GetStatsPrefix().c_str());
return 1;
}
static int SetStatsPrefix(T* p, lua_State* L)
{
RString prefix= SArg(1);
p->SetStatsPrefix(prefix);
COMMON_RETURN_SELF;
}
static int IsPersistentProfile( T* p, lua_State *L ) { lua_pushboolean(L, p->IsPersistentProfile(Enum::Check<PlayerNumber>(L, 1)) ); return 1; }
static int GetProfile( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1); Profile* pP = p->GetProfile(pn); ASSERT(pP != nullptr); pP->PushSelf(L); return 1; }
static int GetMachineProfile( T* p, lua_State *L ) { p->GetMachineProfile()->PushSelf(L); return 1; }
static int SaveMachineProfile( T* p, lua_State * ) { p->SaveMachineProfile(); return 0; }
static int GetLocalProfile( T* p, lua_State *L )
{
Profile *pProfile = p->GetLocalProfile(SArg(1));
if( pProfile )
pProfile->PushSelf(L);
else
lua_pushnil(L);
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 == nullptr)
{
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; }
static int IsSongNew( T* p, lua_State *L ) { lua_pushboolean(L, p->IsSongNew(Luna<Song>::check(L,1)) ); return 1; }
static int ProfileWasLoadedFromMemoryCard( T* p, lua_State *L ) { lua_pushboolean(L, p->ProfileWasLoadedFromMemoryCard(Enum::Check<PlayerNumber>(L, 1)) ); return 1; }
static int LastLoadWasTamperedOrCorrupt( T* p, lua_State *L ) { lua_pushboolean(L, p->LastLoadWasTamperedOrCorrupt(Enum::Check<PlayerNumber>(L, 1)) ); return 1; }
static int GetPlayerName( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1); lua_pushstring(L, p->GetPlayerName(pn)); return 1; }
static int LocalProfileIDToDir( T* , lua_State *L )
{
RString dir = USER_PROFILES_DIR + SArg(1) + "/";
lua_pushstring( L, dir );
return 1;
}
static int SaveProfile( T* p, lua_State *L ) { lua_pushboolean( L, p->SaveProfile(Enum::Check<PlayerNumber>(L, 1)) ); return 1; }
static int SaveLocalProfile( T* p, lua_State *L ) { lua_pushboolean( L, p->SaveLocalProfile(SArg(1)) ); return 1; }
static int ProfileFromMemoryCardIsNew( T* p, lua_State *L ) { lua_pushboolean( L, p->ProfileFromMemoryCardIsNew(Enum::Check<PlayerNumber>(L, 1)) ); return 1; }
static int GetSongNumTimesPlayed( T* p, lua_State *L )
{
lua_pushnumber(L, p->GetSongNumTimesPlayed(Luna<Song>::check(L,1),Enum::Check<ProfileSlot>(L, 2)) );
return 1;
}
static int GetLocalProfileIDs( T* p, lua_State *L )
{
vector<RString> vsProfileIDs;
p->GetLocalProfileIDs(vsProfileIDs);
LuaHelpers::CreateTableFromArray<RString>( vsProfileIDs, L );
return 1;
}
static int GetLocalProfileDisplayNames( T* p, lua_State *L )
{
vector<RString> vsProfileNames;
p->GetLocalProfileDisplayNames(vsProfileNames);
LuaHelpers::CreateTableFromArray<RString>( vsProfileNames, L );
return 1;
}
LunaProfileManager()
{
ADD_METHOD(GetStatsPrefix);
ADD_METHOD(SetStatsPrefix);
ADD_METHOD( IsPersistentProfile );
ADD_METHOD( GetProfile );
ADD_METHOD( GetMachineProfile );
ADD_METHOD( SaveMachineProfile );
ADD_METHOD( GetLocalProfile );
ADD_METHOD( GetLocalProfileFromIndex );
ADD_METHOD( GetLocalProfileIDFromIndex );
ADD_METHOD( GetLocalProfileIndexFromID );
ADD_METHOD( GetNumLocalProfiles );
ADD_METHOD( GetProfileDir );
ADD_METHOD( IsSongNew );
ADD_METHOD( ProfileWasLoadedFromMemoryCard );
ADD_METHOD( LastLoadWasTamperedOrCorrupt );
ADD_METHOD( GetPlayerName );
//
ADD_METHOD( SaveProfile );
ADD_METHOD( SaveLocalProfile );
ADD_METHOD( ProfileFromMemoryCardIsNew );
ADD_METHOD( GetSongNumTimesPlayed );
ADD_METHOD( GetLocalProfileIDs );
ADD_METHOD( GetLocalProfileDisplayNames );
ADD_METHOD( LocalProfileIDToDir );
}
};
LUA_REGISTER_CLASS( ProfileManager )
// lua end
/*
* (c) 2003-2004 Chris Danford
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/