diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 8e2dffe82b..7a74964845 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -381,6 +381,11 @@ CoinsPerCredit=The number of coins that must be inserted before a player can joi ComboContinuesBetweenSongs=If turned on, the combo will not be reset to 0 after every song. Connection=Connect to Server (To be specified) CourseSortOrder=Determines how courses are sorted. +CustomSongsEnable=Custom songs are loaded from USB profiles. +CustomSongsMaxCount=Only this many custom songs will be loaded from a profile. +CustomSongsLoadTimeout=If loading a custom song takes more seconds than this, give up. +CustomSongsMaxSeconds=If a custom song is longer than this (seconds), disable it. +CustomSongsMaxMegabytes=If a custom song is larger than this (MB), disable it. CreateNew= Create New Course=Create a new course. Create New Profile=Create a new player profile. @@ -662,25 +667,37 @@ x2.5=2.5x 2m=2m 2nd=2nd 3=3 +30=30 3 Times=3 Times 32bit=32bit 32nd=32nd 4=4 +40=40 4 Times=4 Times 48th=48th 4th=4th 4m=4m 5=5 +50=50 5 Times=5 Times 6=6 +60=60 64th=64th 7=7 +70=70 8=8 +80=80 8th=8th 9=9 -50=50 +90=90 100=100 +120=120 +150=150 +180=180 +210=210 +240=240 1000=1000 +10000=10000 1000000=1000000 Actual=Actual Add=Additive @@ -1016,6 +1033,11 @@ Create New=Create New Create New Course=Create New Create New Profile=Create Profile Credit=Credit +CustomSongsEnable=Enable Custom Songs +CustomSongsMaxCount=Max Custom Songs +CustomSongsLoadTimeout=Custom Song Load Timeout +CustomSongsMaxSeconds=Custom Song Length Limit +CustomSongsMaxMegabytes=Custom Song Size Limit Cut=Cut DancePointsForOni=Oni Score Display DefaultFailType=Default Fail Type diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index 010f165d24..c6ffd0e0bf 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -138,6 +138,9 @@ CourseGroupColor7=color("1,1,1,1") -- white CourseGroupColor8=color("1,1,1,1") -- white CourseGroupColor9=color("1,1,1,1") -- white CourseGroupColor10=color("1,1,1,1") -- white +NumProfileSongGroupColors=2 +ProfileSongGroupColor1=PlayerColor(PLAYER_1) +ProfileSongGroupColor2=PlayerColor(PLAYER_2) UnlockColor=color("1,0.5,0,1") [UnlockManager] diff --git a/src/GameState.cpp b/src/GameState.cpp index 1fe3ed4a74..31dac86aa3 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -1,6 +1,7 @@ #include "global.h" #include "GameState.h" #include "Actor.h" +#include "ActorUtil.h" #include "AdjustSync.h" #include "AnnouncerManager.h" #include "Bookkeeper.h" @@ -27,6 +28,7 @@ #include "Profile.h" #include "ProfileManager.h" #include "RageFile.h" +#include "RageFileManager.h" #include "RageLog.h" #include "RageUtil.h" #include "Song.h" @@ -1422,6 +1424,60 @@ int GameState::GetLoadingCourseSongIndex() const return iIndex; } +static char const* prepare_song_failures[]= { + "success", + "no_current_song", + "card_mount_failed", + "load_interrupted", +}; + +int GameState::prepare_song_for_gameplay() +{ + Song* curr= m_pCurSong; + if(curr == NULL) + { + return 1; + } + if(curr->m_LoadedFromProfile == ProfileSlot_Invalid) + { + return 0; + } + ProfileSlot prof_slot= curr->m_LoadedFromProfile; + PlayerNumber slot_as_pn= PlayerNumber(prof_slot); + if(!PROFILEMAN->ProfileWasLoadedFromMemoryCard(slot_as_pn)) + { + return 0; + } + if(!MEMCARDMAN->MountCard(slot_as_pn)) + { + return 2; + } + RString prof_dir= PROFILEMAN->GetProfileDir(prof_slot); + // Song loading changes its paths to point to the cache area. -Kyz + RString to_dir= curr->GetSongDir(); + RString from_dir= curr->GetPreCustomifyDir(); + // The problem of what files to copy is complicated by steps being able to + // specify their own music file, and the variety of step file formats. + // Complex logic to figure out what files the song actually uses would be + // bug prone. Just copy all audio files and step files. -Kyz + vector copy_exts= ActorUtil::GetTypeExtensionList(FT_Sound); + copy_exts.push_back("sm"); + copy_exts.push_back("ssc"); + copy_exts.push_back("lrc"); + vector files_in_dir; + FILEMAN->GetDirListingWithMultipleExtensions(from_dir, copy_exts, files_in_dir); + for(size_t i= 0; i < files_in_dir.size(); ++i) + { + RString& fname= files_in_dir[i]; + if(!FileCopy(from_dir + fname, to_dir + fname)) + { + return 3; + } + } + MEMCARDMAN->UnmountCard(slot_as_pn); + return 0; +} + static LocalizedString PLAYER1 ("GameState","Player 1"); static LocalizedString PLAYER2 ("GameState","Player 2"); static LocalizedString CPU ("GameState","CPU"); @@ -3229,6 +3285,12 @@ public: p->m_autogen_fargs[si]= v; COMMON_RETURN_SELF; } + static int prepare_song_for_gameplay(T* p, lua_State* L) + { + int result= p->prepare_song_for_gameplay(); + lua_pushstring(L, prepare_song_failures[result]); + return 1; + } LunaGameState() { @@ -3356,6 +3418,7 @@ public: ADD_METHOD( SetStepsForEditMode ); ADD_METHOD( GetAutoGenFarg ); ADD_METHOD( SetAutoGenFarg ); + ADD_METHOD(prepare_song_for_gameplay); } }; diff --git a/src/GameState.h b/src/GameState.h index b5fbf702dc..35c230b9bc 100644 --- a/src/GameState.h +++ b/src/GameState.h @@ -242,6 +242,8 @@ public: bool m_bLoadingNextSong; int GetLoadingCourseSongIndex() const; + int prepare_song_for_gameplay(); + // State Info used during gameplay // NULL on ScreenSelectMusic if the currently selected wheel item isn't a Song. diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index 0051305b47..fd2b1bae11 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -47,6 +47,7 @@ namespace LuaHelpers template<> bool FromStack( Lua *L, bool &Object, int iOffset ); template<> bool FromStack( Lua *L, float &Object, int iOffset ); template<> bool FromStack( Lua *L, int &Object, int iOffset ); + template<> bool FromStack( Lua *L, unsigned int &Object, int iOffset ); template<> bool FromStack( Lua *L, RString &Object, int iOffset ); bool InReportScriptError= false; @@ -88,6 +89,7 @@ namespace LuaHelpers template<> bool FromStack( Lua *L, bool &Object, int iOffset ) { Object = !!lua_toboolean( L, iOffset ); return true; } template<> bool FromStack( Lua *L, float &Object, int iOffset ) { Object = (float)lua_tonumber( L, iOffset ); return true; } template<> bool FromStack( Lua *L, int &Object, int iOffset ) { Object = lua_tointeger( L, iOffset ); return true; } + template<> bool FromStack( Lua *L, unsigned int &Object, int iOffset ) { Object = lua_tointeger( L, iOffset ); return true; } template<> bool FromStack( Lua *L, RString &Object, int iOffset ) { size_t iLen; diff --git a/src/MusicWheel.cpp b/src/MusicWheel.cpp index 6c02557988..fa3c96258f 100644 --- a/src/MusicWheel.cpp +++ b/src/MusicWheel.cpp @@ -9,6 +9,7 @@ #include "GameState.h" #include "ThemeManager.h" #include "NetworkSyncManager.h" +#include "ProfileManager.h" #include "Song.h" #include "Course.h" #include "Steps.h" @@ -434,6 +435,18 @@ void MusicWheel::GetSongList( vector &arraySongs, SortOrder so ) break; } + FOREACH_PlayerNumber(pn) + { + if(GAMESTATE->IsPlayerEnabled(pn)) + { + Profile* prof= PROFILEMAN->GetProfile(pn); + for(size_t i= 0; i < prof->m_songs.size(); ++i) + { + apAllSongs.push_back(prof->m_songs[i]); + } + } + } + // filter songs that we don't have enough stages to play { vector vTempSongs; diff --git a/src/PrefsManager.cpp b/src/PrefsManager.cpp index 0c8f2596f9..e733a5e3f7 100644 --- a/src/PrefsManager.cpp +++ b/src/PrefsManager.cpp @@ -299,6 +299,12 @@ PrefsManager::PrefsManager() : m_bQuirksMode ( "QuirksMode", false ), + m_custom_songs_enable("CustomSongsEnable", false), + m_custom_songs_max_count("CustomSongsMaxCount", 1000), // No limit. -- 2 Unlimited + m_custom_songs_load_timeout("CustomSongsLoadTimeout", 5.f), + m_custom_songs_max_seconds("CustomSongsMaxSeconds", 120.f), + m_custom_songs_max_megabytes("CustomSongsMaxMegabytes", 5.f), + /* Debug: */ m_bLogToDisk ( "LogToDisk", true ), #if defined(DEBUG) diff --git a/src/PrefsManager.h b/src/PrefsManager.h index bd952e2df3..746506eeaa 100644 --- a/src/PrefsManager.h +++ b/src/PrefsManager.h @@ -309,6 +309,12 @@ public: /** @brief Enable some quirky behavior used by some older versions of StepMania. */ Preference m_bQuirksMode; + Preference m_custom_songs_enable; + Preference m_custom_songs_max_count; + Preference m_custom_songs_load_timeout; + Preference m_custom_songs_max_seconds; + Preference m_custom_songs_max_megabytes; + // Debug: Preference m_bLogToDisk; Preference m_bForceLogFlush; diff --git a/src/Profile.cpp b/src/Profile.cpp index df9f8243df..97a7704448 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -5,6 +5,7 @@ #include "XmlFile.h" #include "IniFile.h" #include "GameManager.h" +#include "GameState.h" #include "RageLog.h" #include "Song.h" #include "SongManager.h" @@ -72,6 +73,31 @@ StringToX(ProfileType); LuaXType(ProfileType); + +Profile::~Profile() +{ + ClearSongs(); +} + +void Profile::ClearSongs() +{ + if(m_songs.empty()) + { + return; + } + Song* gamestate_curr_song= GAMESTATE->m_pCurSong; + for(size_t i= 0; i < m_songs.size(); ++i) + { + Song* curr_song= m_songs[i]; + if(curr_song == gamestate_curr_song) + { + GAMESTATE->m_pCurSong.Set(NULL); + } + delete curr_song; + } + m_songs.clear(); +} + int Profile::HighScoresForASong::GetNumTimesPlayed() const { int iCount = 0; @@ -1044,7 +1070,7 @@ void Profile::IncrementCategoryPlayCount( StepsType st, RankingCategory rc ) if( X==NULL ) LOG->Warn("Failed to read section " #X); \ else Load##X##FromNode(X); } -void Profile::LoadCustomFunction( RString sDir ) +void Profile::LoadCustomFunction(RString sDir, PlayerNumber pn) { /* Get the theme's custom load function: * [Profile] @@ -1058,10 +1084,18 @@ void Profile::LoadCustomFunction( RString sDir ) // Pass profile and profile directory as arguments this->PushSelf(L); LuaHelpers::Push(L, sDir); + if(pn == PlayerNumber_Invalid) + { + lua_pushnil(L); + } + else + { + Enum::Push(L, pn); + } // Run it RString Error= "Error running CustomLoadFunction: "; - LuaHelpers::RunScriptOnStack(L, Error, 2, 0, true); + LuaHelpers::RunScriptOnStack(L, Error, 3, 0, true); LUA->Release(L); } @@ -1148,11 +1182,65 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature if (ret != ProfileLoadResult_Success) return ret; - LoadCustomFunction( sDir ); + LoadCustomFunction(sDir, PlayerNumber_Invalid); return ProfileLoadResult_Success; } + +// Custom songs are not stored with all the normal songs because walking the +// entire song list to remove custom songs when unloading the profile is +// wasteful. -Kyz + +void Profile::LoadSongsFromDir(RString const& dir, ProfileSlot prof_slot) +{ + if(!PREFSMAN->m_custom_songs_enable) + { + return; + } + RString songs_folder= dir + "Songs"; + if(FILEMAN->DoesFileExist(songs_folder)) + { + LOG->Trace("Found songs folder in profile."); + vector song_folders; + RageTimer song_load_start_time; + song_load_start_time.Touch(); + FILEMAN->GetDirListing(songs_folder + "/*", song_folders, true, true); + StripCvsAndSvn(song_folders); + StripMacResourceForks(song_folders); + LOG->Trace("Found %i songs in profile.", int(song_folders.size())); + // Only songs that are successfully loaded count towards the limit. -Kyz + for(size_t song_index= 0; song_index < song_folders.size() + && m_songs.size() < PREFSMAN->m_custom_songs_max_count; + ++song_index) + { + RString& song_dir_name= song_folders[song_index]; + Song* new_song= new Song; + if(!new_song->LoadFromSongDir(song_dir_name, false, prof_slot)) + { + // The song failed to load. + LOG->Trace("Song %s failed to load.", song_dir_name.c_str()); + delete new_song; + } + else + { + new_song->SetEnabled(true); + m_songs.push_back(new_song); + } + if(song_load_start_time.Ago() > PREFSMAN->m_custom_songs_load_timeout) + { + break; + } + } + float load_time= song_load_start_time.Ago(); + LOG->Trace("Successfully loaded %zu songs in %.6f from profile.", m_songs.size(), load_time); + } + else + { + LOG->Trace("No songs folder in profile."); + } +} + ProfileLoadResult Profile::LoadStatsFromDir(RString dir, bool require_signature) { dir= dir + PROFILEMAN->GetStatsPrefix(); @@ -2699,7 +2787,17 @@ public: return 1; } DEFINE_METHOD( GetGUID, m_sGuid ); - + static int get_songs(T* p, lua_State* L) + { + lua_createtable(L, p->m_songs.size(), 0); + int song_tab= lua_gettop(L); + for(size_t i= 0; i < p->m_songs.size(); ++i) + { + p->m_songs[i]->PushSelf(L); + lua_rawseti(L, song_tab, i+1); + } + return 1; + } LunaProfile() { ADD_METHOD( AddScreenshot ); @@ -2769,6 +2867,7 @@ public: ADD_METHOD( GetLastPlayedSong ); ADD_METHOD( GetLastPlayedCourse ); ADD_METHOD( GetGUID ); + ADD_METHOD(get_songs); } }; diff --git a/src/Profile.h b/src/Profile.h index 45c17bd642..267ccc71dc 100644 --- a/src/Profile.h +++ b/src/Profile.h @@ -14,6 +14,7 @@ #include "TrailUtil.h" // for TrailID #include "StyleUtil.h" // for StyleID #include "LuaReference.h" +#include "PlayerNumber.h" class XNode; struct lua_State; @@ -134,6 +135,10 @@ public: m_CategoryHighScores[st][rc].Init(); } + + ~Profile(); + void ClearSongs(); + // smart accessors RString GetDisplayNameOrHighScoreName() const; Character *GetCharacter() const; @@ -196,6 +201,7 @@ public: RString m_sGuid; map m_sDefaultModifiers; SortOrder m_SortOrder; + std::vector m_songs; Difficulty m_LastDifficulty; CourseDifficulty m_LastCourseDifficulty; StepsType m_LastStepsType; @@ -376,7 +382,8 @@ public: InitCourseScores(); InitCategoryScores(); InitScreenshotData(); - InitCalorieData(); + InitCalorieData(); + ClearSongs(); } void InitEditableData(); void InitGeneralData(); @@ -393,8 +400,9 @@ public: void HandleStatsPrefixChange(RString dir, bool require_signature); ProfileLoadResult LoadAllFromDir( RString sDir, bool bRequireSignature ); ProfileLoadResult LoadStatsFromDir(RString dir, bool require_signature); + void LoadSongsFromDir(RString const& dir, ProfileSlot prof_slot); void LoadTypeFromDir(RString dir); - void LoadCustomFunction( RString sDir ); + void LoadCustomFunction(RString sDir, PlayerNumber pn); bool SaveAllToDir( RString sDir, bool bSignData ) const; ProfileLoadResult LoadEditableDataFromDir( RString sDir ); diff --git a/src/ProfileManager.cpp b/src/ProfileManager.cpp index c673e61a0e..331a52bc38 100644 --- a/src/ProfileManager.cpp +++ b/src/ProfileManager.cpp @@ -187,6 +187,17 @@ ProfileLoadResult ProfileManager::LoadProfile( PlayerNumber pn, RString sProfile } } + 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; @@ -211,7 +222,7 @@ bool ProfileManager::LoadLocalProfileFromMachine( PlayerNumber pn ) return false; } - GetProfile(pn)->LoadCustomFunction( m_sProfileDir[pn] ); + GetProfile(pn)->LoadCustomFunction(m_sProfileDir[pn], pn); return true; } diff --git a/src/RageUtil.cpp b/src/RageUtil.cpp index 644b97e8ed..8a58bcb2bd 100644 --- a/src/RageUtil.cpp +++ b/src/RageUtil.cpp @@ -18,6 +18,8 @@ #include #include +const RString CUSTOM_SONG_PATH= "/@mem/"; + bool HexToBinary(const RString&, RString&); void utf8_sanitize(RString &); void UnicodeUpperLower(wchar_t *, size_t, const unsigned char *); @@ -979,6 +981,17 @@ void splitpath( const RString &sPath, RString &sDir, RString &sFilename, RString } } +RString custom_songify_path(RString const& path) +{ + vector parts; + split(path, "/", parts, false); + if(parts.size() < 2) + { + return CUSTOM_SONG_PATH + path; + } + return CUSTOM_SONG_PATH + parts[parts.size()-2] + "/" + parts[parts.size()-1]; +} + /* "foo.bar", "baz" -> "foo.baz" * "foo", "baz" -> "foo.baz" * "foo.bar", "" -> "foo" */ diff --git a/src/RageUtil.h b/src/RageUtil.h index 5895fe9498..40e57e562e 100644 --- a/src/RageUtil.h +++ b/src/RageUtil.h @@ -20,6 +20,8 @@ class RageFileDriver; /** @brief Get the length of the array. */ #define ARRAYLEN(a) (sizeof(a) / sizeof((a)[0])) +extern const RString CUSTOM_SONG_PATH; + /* Common harmless mismatches. All min(T,T) and max(T,T) cases are handled * by the generic template we get from . */ inline float min( float a, int b ) { return a < b? a:b; } @@ -377,6 +379,7 @@ RString ConvertI64FormatString( const RString &sStr ); * element will end up in Dir, not FName: "c:\games\stepmania\". * */ void splitpath( const RString &Path, RString &Dir, RString &Filename, RString &Ext ); +RString custom_songify_path(RString const& path); RString SetExtension( const RString &path, const RString &ext ); RString GetExtension( const RString &sPath ); diff --git a/src/ScreenOptionsMasterPrefs.cpp b/src/ScreenOptionsMasterPrefs.cpp index 2ed8bcc8f7..e255c91e95 100644 --- a/src/ScreenOptionsMasterPrefs.cpp +++ b/src/ScreenOptionsMasterPrefs.cpp @@ -704,6 +704,29 @@ static void EditClearPromptThreshold(int& sel, bool to_sel, const ConfOption* co MoveMap(sel, conf_option, to_sel, mapping, ARRAYLEN(mapping)); } +static void CustomSongsCount(int& sel, bool to_sel, const ConfOption* conf_option) +{ + int mapping[]= {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000}; + MoveMap(sel, conf_option, to_sel, mapping, ARRAYLEN(mapping)); +} + +static void CustomSongsLoadTimeout(int& sel, bool to_sel, const ConfOption* conf_option) +{ + int mapping[]= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 1000}; + MoveMap(sel, conf_option, to_sel, mapping, ARRAYLEN(mapping)); +} + +static void CustomSongsMaxSeconds(int& sel, bool to_sel, const ConfOption* conf_option) +{ + int mapping[]= {60, 90, 120, 150, 180, 210, 240, 10000}; + MoveMap(sel, conf_option, to_sel, mapping, ARRAYLEN(mapping)); +} + +static void CustomSongsMaxMegabytes(int& sel, bool to_sel, const ConfOption* conf_option) +{ + int mapping[]= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 1000}; + MoveMap(sel, conf_option, to_sel, mapping, ARRAYLEN(mapping)); +} static vector g_ConfOptions; static void InitializeConfOptions() { @@ -808,6 +831,11 @@ static void InitializeConfOptions() ADD( ConfOption( "PickExtraStage", MovePref, "Off","On" ) ); ADD( ConfOption( "UseUnlockSystem", MovePref, "Off","On" ) ); ADD( ConfOption( "AllowSongDeletion", MovePref, "Off","On" ) ); + ADD(ConfOption("CustomSongsEnable", MovePref, "Off", "On")); + ADD(ConfOption("CustomSongsMaxCount", CustomSongsCount, "10", "20", "30", "40", "50", "60", "70", "80", "90", "100", "1000")); + ADD(ConfOption("CustomSongsLoadTimeout", CustomSongsLoadTimeout, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "20", "30", "1000")); + ADD(ConfOption("CustomSongsMaxSeconds", CustomSongsMaxSeconds, "60", "90", "120", "150", "180", "210", "240", "10000")); + ADD(ConfOption("CustomSongsMaxMegabytes", CustomSongsLoadTimeout, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "20", "30", "1000")); // Machine options ADD( ConfOption( "MenuTimer", MovePref, "Off","On" ) ); diff --git a/src/ScreenSelectMusic.cpp b/src/ScreenSelectMusic.cpp index 82f0784048..eedc0ad469 100644 --- a/src/ScreenSelectMusic.cpp +++ b/src/ScreenSelectMusic.cpp @@ -1498,6 +1498,7 @@ bool ScreenSelectMusic::MenuStart( const InputEventPlus &input ) // Now that Steps have been chosen, set a Style that can play them. GAMESTATE->SetCompatibleStylesForPlayers(); GAMESTATE->ForceSharedSidesMatch(); + GAMESTATE->prepare_song_for_gameplay(); /* If we're currently waiting on song assets, abort all except the music * and start the music, so if we make a choice quickly before background diff --git a/src/Song.cpp b/src/Song.cpp index 3f86d04ddb..cb696be5a6 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -14,7 +14,9 @@ #include "TitleSubstitution.h" #include "BannerCache.h" //#include "BackgroundCache.h" +#include "ProfileManager.h" #include "Sprite.h" +#include "RageFile.h" #include "RageFileManager.h" #include "RageSurface.h" #include "RageTextureManager.h" @@ -271,7 +273,7 @@ static set BlacklistedImages; * HasMusic(), HasBanner() or GetHashForDirectory(). * If true, check the directory hash and reload the song from scratch if it's changed. */ -bool Song::LoadFromSongDir( RString sDir, bool load_autosave ) +bool Song::LoadFromSongDir(RString sDir, bool load_autosave, ProfileSlot from_profile) { // LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.c_str() ); ASSERT_M( sDir != "", "Songs can't be loaded from an empty directory!" ); @@ -283,26 +285,40 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave ) // save song dir m_sSongDir = sDir; + bool use_cache = true; + // save group name - vector sDirectoryParts; - split( m_sSongDir, "/", sDirectoryParts, false ); - ASSERT( sDirectoryParts.size() >= 4 ); /* e.g. "/Songs/Slow/Taps/" */ - m_sGroupName = sDirectoryParts[sDirectoryParts.size()-3]; // second from last item - ASSERT( m_sGroupName != "" ); + if(from_profile == ProfileSlot_Invalid) + { + vector sDirectoryParts; + split( m_sSongDir, "/", sDirectoryParts, false ); + ASSERT( sDirectoryParts.size() >= 4 ); /* e.g. "/Songs/Slow/Taps/" */ + m_sGroupName = sDirectoryParts[sDirectoryParts.size()-3]; // second from last item + ASSERT( m_sGroupName != "" ); + } + else + { + m_LoadedFromProfile= from_profile; + m_sGroupName= PROFILEMAN->GetProfile(from_profile)->m_sDisplayName; + use_cache= false; + } - // First, look in the cache for this song (without loading NoteData) - unsigned uCacheHash = SONGINDEX->GetCacheHash(m_sSongDir); - bool bUseCache = true; - RString sCacheFilePath = GetCacheFilePath(); + RString cache_file_path; + if(m_LoadedFromProfile == ProfileSlot_Invalid) + { + // First, look in the cache for this song (without loading NoteData) + unsigned uCacheHash = SONGINDEX->GetCacheHash(m_sSongDir); + cache_file_path = GetCacheFilePath(); - if( !DoesFileExist(sCacheFilePath) ) - { bUseCache = false; } - else if(!PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sSongDir) != uCacheHash) - { bUseCache = false; } // this cache is out of date - else if(load_autosave) - { bUseCache= false; } + if( !DoesFileExist(cache_file_path) ) + { use_cache = false; } + else if(!PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sSongDir) != uCacheHash) + { use_cache = false; } // this cache is out of date + else if(load_autosave) + { use_cache= false; } + } - if( bUseCache ) + if(use_cache) { /* LOG->Trace("Loading '%s' from cache file '%s'.", @@ -310,12 +326,12 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave ) GetCacheFilePath().c_str()); */ SSCLoader loaderSSC; - bool bLoadedFromSSC = loaderSSC.LoadFromSimfile( sCacheFilePath, *this, true ); + bool bLoadedFromSSC = loaderSSC.LoadFromSimfile( cache_file_path, *this, true ); if( !bLoadedFromSSC ) { // load from .sm SMLoader loaderSM; - loaderSM.LoadFromSimfile( sCacheFilePath, *this, true ); + loaderSM.LoadFromSimfile( cache_file_path, *this, true ); loaderSM.TidyUpData( *this, true ); } if(m_sMainTitle == "" || (m_sMusicFile == "" && m_vsKeysoundFile.empty())) @@ -335,13 +351,12 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave ) { LOG->UserLog( "Song", sDir, "has no SSC, SM, SMA, DWI, BMS, or KSF files." ); - vector vs; - GetDirListing( sDir + "*.mp3", vs, false, false ); - GetDirListing( sDir + "*.oga", vs, false, false ); - GetDirListing( sDir + "*.ogg", vs, false, false ); - bool bHasMusic = !vs.empty(); + vector audios; + FILEMAN->GetDirListingWithMultipleExtensions(sDir, + ActorUtil::GetTypeExtensionList(FT_Sound), audios); + bool has_music = !audios.empty(); - if( !bHasMusic ) + if(!has_music) { LOG->UserLog( "Song", sDir, "has no music file either. Ignoring this song directory." ); return false; @@ -361,16 +376,52 @@ bool Song::LoadFromSongDir( RString sDir, bool load_autosave ) // Don't save a cache file if the autosave is being loaded, because the // cache file would contain the autosave filename. -Kyz - if(!load_autosave) + // Songs loaded from removable profile are never cached, on the + // assumption that they'll change frequently and have invalid cache + // entries. -Kyz + if(!load_autosave && m_LoadedFromProfile == ProfileSlot_Invalid) { // save a cache file so we don't have to parse it all over again next time if(!SaveToCacheFile()) - { sCacheFilePath = RString(); } + { cache_file_path = RString(); } } } + if(m_LoadedFromProfile != ProfileSlot_Invalid) + { + if(m_fMusicLengthSeconds > PREFSMAN->m_custom_songs_max_seconds) + { + LOG->Trace("Custom song %s is too long.", m_sSongDir.c_str()); + return false; + } + RageFile music; + if(!music.Open(GetMusicPath(), RageFile::READ)) + { + LOG->Trace("Custom song %s could not open music.", m_sSongDir.c_str()); + return false; + } + if(music.GetFileSize() > PREFSMAN->m_custom_songs_max_megabytes * 1000000) + { + LOG->Trace("Custom song %s music file is too big.", m_sSongDir.c_str()); + return false; + } + m_pre_customify_song_dir= m_sSongDir; + m_sSongDir= custom_songify_path(m_sSongDir); + m_sBannerFile.clear(); + m_sJacketFile.clear(); + m_sCDFile.clear(); + m_sDiscFile.clear(); + m_sLyricsFile.clear(); + m_sBackgroundFile.clear(); + m_sCDTitleFile.clear(); + } + FOREACH( Steps*, m_vpSteps, s ) { + if(m_LoadedFromProfile != ProfileSlot_Invalid) + { + (*s)->ChangeFilenamesForCustomSong(); + } /* Compress all Steps. During initial caching, this will remove cached * NoteData; during cached loads, this will just remove cached SMData. */ (*s)->Compress(); @@ -653,10 +704,15 @@ void Song::TidyUpData( bool from_cache, bool /* duringCache */ ) fill_exts.reserve(4); lists_to_fill.push_back(&music_list); fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Sound)); - lists_to_fill.push_back(&image_list); - fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Bitmap)); - lists_to_fill.push_back(&movie_list); - fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Movie)); + // Disable bg and banner images and movies for custom songs. Loading + // them takes too long. -Kyz + if(m_LoadedFromProfile == ProfileSlot_Invalid) + { + lists_to_fill.push_back(&image_list); + fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Bitmap)); + lists_to_fill.push_back(&movie_list); + fill_exts.push_back(&ActorUtil::GetTypeExtensionList(FT_Movie)); + } lists_to_fill.push_back(&lyric_list); fill_exts.push_back(&lyric_extensions); for(vector::iterator filename= song_dir_listing.begin(); @@ -801,85 +857,6 @@ void Song::TidyUpData( bool from_cache, bool /* duringCache */ ) } - // Here's the problem: We have a directory full of images. We want to - // determine which image is the banner, which is the background, and - // which is the CDTitle. - - // For blank args to FindFirstFilenameContaining. -Kyz - vector empty_list; - - bool has_jacket= HasJacket(); - bool has_cdimage= HasCDImage(); - bool has_disc= HasDisc(); - bool has_cdtitle= HasCDTitle(); - - // First, check the file name for hints. - if(!m_bHasBanner) - { - /* If a nonexistant banner file is specified, and we can't find a - * replacement, don't wipe out the old value. */ - //m_sBannerFile = ""; - - // find an image with "banner" in the file name - vector contains(1, "banner"); - /* Some people do things differently for the sake of being different. - * Don't match eg. abnormal, numbness. */ - vector ends_with(1, " bn"); - m_bHasBanner= FindFirstFilenameContaining(image_list, - m_sBannerFile, empty_list, contains, ends_with); - } - - if(!m_bHasBackground) - { - //m_sBackgroundFile = ""; - - // find an image with "bg" or "background" in the file name - vector contains(1, "background"); - vector ends_with(1, "bg"); - m_bHasBackground= FindFirstFilenameContaining(image_list, - m_sBackgroundFile, empty_list, contains, ends_with); - } - - if(!has_jacket) - { - // find an image with "jacket" or "albumart" in the filename. - vector starts_with(1, "jk_"); - vector contains; - contains.reserve(2); - contains.push_back("jacket"); - contains.push_back("albumart"); - has_jacket= FindFirstFilenameContaining(image_list, - m_sJacketFile, starts_with, contains, empty_list); - } - - if(!has_cdimage) - { - // CD image, a la ddr 1st-3rd (not to be confused with CDTitles) - // find an image with "-cd" at the end of the filename. - vector ends_with(1, "-cd"); - has_cdimage= FindFirstFilenameContaining(image_list, - m_sCDFile, empty_list, empty_list, ends_with); - } - - if(!has_disc) - { - // a rectangular graphic, not to be confused with CDImage above. - vector ends_with; - ends_with.reserve(2); - ends_with.push_back(" disc"); - ends_with.push_back(" title"); - has_disc= FindFirstFilenameContaining(image_list, - m_sDiscFile, empty_list, empty_list, ends_with); - } - - if(!has_cdtitle) - { - // find an image with "cdtitle" in the file name - vector contains(1, "cdtitle"); - has_cdtitle= FindFirstFilenameContaining(image_list, - m_sCDTitleFile, empty_list, contains, empty_list); - } - if(!HasLyrics()) { // Check if there is a lyric file in here @@ -889,155 +866,238 @@ void Song::TidyUpData( bool from_cache, bool /* duringCache */ ) } } - /* Now, For the images we still haven't found, - * look at the image dimensions of the remaining unclassified images. */ - for(unsigned int i= 0; i < image_list.size(); ++i) // foreach image + // Since banner, bg, and cdtitle are disabled for custom songs, don't try + // to find them. -Kyz + if(m_LoadedFromProfile == ProfileSlot_Invalid) { - if(m_bHasBanner && m_bHasBackground && has_cdtitle) + // Here's the problem: We have a directory full of images. We want to + // determine which image is the banner, which is the background, and + // which is the CDTitle. + + // For blank args to FindFirstFilenameContaining. -Kyz + vector empty_list; + + bool has_jacket= HasJacket(); + bool has_cdimage= HasCDImage(); + bool has_disc= HasDisc(); + bool has_cdtitle= HasCDTitle(); + + // First, check the file name for hints. + if(!m_bHasBanner) + { + /* If a nonexistant banner file is specified, and we can't find a + * replacement, don't wipe out the old value. */ + //m_sBannerFile = ""; + + // find an image with "banner" in the file name + vector contains(1, "banner"); + /* Some people do things differently for the sake of being different. + * Don't match eg. abnormal, numbness. */ + vector ends_with(1, " bn"); + m_bHasBanner= FindFirstFilenameContaining(image_list, + m_sBannerFile, empty_list, contains, ends_with); + } + + if(!m_bHasBackground) + { + //m_sBackgroundFile = ""; + + // find an image with "bg" or "background" in the file name + vector contains(1, "background"); + vector ends_with(1, "bg"); + m_bHasBackground= FindFirstFilenameContaining(image_list, + m_sBackgroundFile, empty_list, contains, ends_with); + } + + if(!has_jacket) + { + // find an image with "jacket" or "albumart" in the filename. + vector starts_with(1, "jk_"); + vector contains; + contains.reserve(2); + contains.push_back("jacket"); + contains.push_back("albumart"); + has_jacket= FindFirstFilenameContaining(image_list, + m_sJacketFile, starts_with, contains, empty_list); + } + + if(!has_cdimage) + { + // CD image, a la ddr 1st-3rd (not to be confused with CDTitles) + // find an image with "-cd" at the end of the filename. + vector ends_with(1, "-cd"); + has_cdimage= FindFirstFilenameContaining(image_list, + m_sCDFile, empty_list, empty_list, ends_with); + } + + if(!has_disc) + { + // a rectangular graphic, not to be confused with CDImage above. + vector ends_with; + ends_with.reserve(2); + ends_with.push_back(" disc"); + ends_with.push_back(" title"); + has_disc= FindFirstFilenameContaining(image_list, + m_sDiscFile, empty_list, empty_list, ends_with); + } + + if(!has_cdtitle) + { + // find an image with "cdtitle" in the file name + vector contains(1, "cdtitle"); + has_cdtitle= FindFirstFilenameContaining(image_list, + m_sCDTitleFile, empty_list, contains, empty_list); + } + + /* Now, For the images we still haven't found, + * look at the image dimensions of the remaining unclassified images. */ + for(unsigned int i= 0; i < image_list.size(); ++i) // foreach image + { + if(m_bHasBanner && m_bHasBackground && has_cdtitle) break; // done - // ignore DWI "-char" graphics - RString lower = image_list[i]; - lower.MakeLower(); - if(BlacklistedImages.find(lower) != BlacklistedImages.end()) + // ignore DWI "-char" graphics + RString lower = image_list[i]; + lower.MakeLower(); + if(BlacklistedImages.find(lower) != BlacklistedImages.end()) continue; // skip - // Skip any image that we've already classified + // Skip any image that we've already classified - if(m_bHasBanner && m_sBannerFile.EqualsNoCase(image_list[i])) + if(m_bHasBanner && m_sBannerFile.EqualsNoCase(image_list[i])) continue; // skip - if(m_bHasBackground && m_sBackgroundFile.EqualsNoCase(image_list[i])) + if(m_bHasBackground && m_sBackgroundFile.EqualsNoCase(image_list[i])) continue; // skip - if(has_cdtitle && m_sCDTitleFile.EqualsNoCase(image_list[i])) + if(has_cdtitle && m_sCDTitleFile.EqualsNoCase(image_list[i])) continue; // skip - if(has_jacket && m_sJacketFile.EqualsNoCase(image_list[i])) + if(has_jacket && m_sJacketFile.EqualsNoCase(image_list[i])) continue; // skip - if(has_disc && m_sDiscFile.EqualsNoCase(image_list[i])) + if(has_disc && m_sDiscFile.EqualsNoCase(image_list[i])) continue; // skip - if(has_cdimage && m_sCDFile.EqualsNoCase(image_list[i])) + if(has_cdimage && m_sCDFile.EqualsNoCase(image_list[i])) continue; // skip - RString sPath = m_sSongDir + image_list[i]; + RString sPath = m_sSongDir + image_list[i]; - // We only care about the dimensions. - RString error; - RageSurface *img = RageSurfaceUtils::LoadFile(sPath, error, true); - if(!img) - { - LOG->UserLog("Graphic file", sPath, "couldn't be loaded: %s", error.c_str()); - continue; - } - - const int width = img->w; - const int height = img->h; - delete img; - - if(!m_bHasBackground && width >= 320 && height >= 240) - { - m_sBackgroundFile = image_list[i]; - m_bHasBackground= true; - continue; - } - - if(!m_bHasBanner && 100 <= width && width <= 320 && - 50 <= height && height <= 240) - { - m_sBannerFile = image_list[i]; - m_bHasBanner= true; - continue; - } - - /* Some songs have overlarge banners. Check if the ratio is reasonable - * (over 2:1; usually over 3:1), and large (not a cdtitle). */ - if(!m_bHasBanner && width > 200 && float(width) / height > 2.0f) - { - m_sBannerFile = image_list[i]; - m_bHasBanner= true; - continue; - } - - /* Agh. DWI's inline title images are triggering this, resulting in - * kanji, etc., being used as a CDTitle for songs with none. Some - * sample data from random incarnations: - * 42x50 35x50 50x50 144x49 - * It looks like ~50 height is what people use to align to DWI's font. - * - * My tallest CDTitle is 44. Let's cut off in the middle and hope for - * the best. -(who? -aj) */ - /* The proper size of a CDTitle is 64x48 or sometihng. Simfile artists - * typically don't give a shit about this (see Cetaka's fucking banner - * -sized CDTitle). This is also subverted in certain designs (beta - * Mungyodance 3 simfiles, for instance, used the CDTitle to hold - * various information about the song in question). As it stands, - * I'm keeping this code until I figure out wtf to do -aj - */ - if(!has_cdtitle && width <= 100 && height <= 48) - { - m_sCDTitleFile = image_list[i]; - has_cdtitle= true; - continue; - } - - // Jacket files typically have the same width and height. - if(!has_jacket && width == height) - { - m_sJacketFile = image_list[i]; - has_jacket= true; - continue; - } - - // Disc images are typically rectangular; make sure we have a banner already. - if(!has_disc && (width > height) && m_bHasBanner) - { - if(image_list[i] != m_sBannerFile) + // We only care about the dimensions. + RString error; + RageSurface *img = RageSurfaceUtils::LoadFile(sPath, error, true); + if(!img) { - m_sDiscFile = image_list[i]; - has_disc= true; + LOG->UserLog("Graphic file", sPath, "couldn't be loaded: %s", error.c_str()); + continue; } - continue; - } - // CD images are the same as Jackets, typically the same width and height - if(!has_cdimage && width == height) - { - m_sCDFile = image_list[i]; - has_cdimage= true; - continue; + const int width = img->w; + const int height = img->h; + delete img; + + if(!m_bHasBackground && width >= 320 && height >= 240) + { + m_sBackgroundFile = image_list[i]; + m_bHasBackground= true; + continue; + } + + if(!m_bHasBanner && 100 <= width && width <= 320 && + 50 <= height && height <= 240) + { + m_sBannerFile = image_list[i]; + m_bHasBanner= true; + continue; + } + + /* Some songs have overlarge banners. Check if the ratio is reasonable + * (over 2:1; usually over 3:1), and large (not a cdtitle). */ + if(!m_bHasBanner && width > 200 && float(width) / height > 2.0f) + { + m_sBannerFile = image_list[i]; + m_bHasBanner= true; + continue; + } + + /* Agh. DWI's inline title images are triggering this, resulting in + * kanji, etc., being used as a CDTitle for songs with none. Some + * sample data from random incarnations: + * 42x50 35x50 50x50 144x49 + * It looks like ~50 height is what people use to align to DWI's font. + * + * My tallest CDTitle is 44. Let's cut off in the middle and hope for + * the best. -(who? -aj) */ + /* The proper size of a CDTitle is 64x48 or sometihng. Simfile artists + * typically don't give a shit about this (see Cetaka's fucking banner + * -sized CDTitle). This is also subverted in certain designs (beta + * Mungyodance 3 simfiles, for instance, used the CDTitle to hold + * various information about the song in question). As it stands, + * I'm keeping this code until I figure out wtf to do -aj + */ + if(!has_cdtitle && width <= 100 && height <= 48) + { + m_sCDTitleFile = image_list[i]; + has_cdtitle= true; + continue; + } + + // Jacket files typically have the same width and height. + if(!has_jacket && width == height) + { + m_sJacketFile = image_list[i]; + has_jacket= true; + continue; + } + + // Disc images are typically rectangular; make sure we have a banner already. + if(!has_disc && (width > height) && m_bHasBanner) + { + if(image_list[i] != m_sBannerFile) + { + m_sDiscFile = image_list[i]; + has_disc= true; + } + continue; + } + + // CD images are the same as Jackets, typically the same width and height + if(!has_cdimage && width == height) + { + m_sCDFile = image_list[i]; + has_cdimage= true; + continue; + } } - } - // If no BGChanges are specified and there are movies in the song - // directory, then assume they are DWI style where the movie begins at - // beat 0. - if(!HasBGChanges()) - { - /* Use this->GetBeatFromElapsedTime(0) instead of 0 to start when the - * music starts. */ - if(movie_list.size() == 1) + // If no BGChanges are specified and there are movies in the song + // directory, then assume they are DWI style where the movie begins at + // beat 0. + if(!HasBGChanges()) { - this->AddBackgroundChange(BACKGROUND_LAYER_1, - BackgroundChange(0, movie_list[0], "", 1.f, - SBE_StretchNoLoop)); + /* Use this->GetBeatFromElapsedTime(0) instead of 0 to start when the + * music starts. */ + if(movie_list.size() == 1) + { + this->AddBackgroundChange(BACKGROUND_LAYER_1, + BackgroundChange(0, movie_list[0], "", 1.f, + SBE_StretchNoLoop)); + } } + // Clear fields for files that turned out to not exist. +#define CLEAR_NOT_HAS(has_name, field_name) if(!has_name) { field_name= ""; } + CLEAR_NOT_HAS(m_bHasBanner, m_sBannerFile); + CLEAR_NOT_HAS(m_bHasBackground, m_sBackgroundFile); + CLEAR_NOT_HAS(has_jacket, m_sJacketFile); + CLEAR_NOT_HAS(has_cdimage, m_sCDFile); + CLEAR_NOT_HAS(has_disc, m_sDiscFile); + CLEAR_NOT_HAS(has_cdtitle, m_sCDTitleFile); +#undef CLEAR_NOT_HAS } // Don't allow multiple Steps of the same StepsType and Difficulty // (except for edits). We should be able to use difficulty names as // unique identifiers for steps. */ SongUtil::AdjustDuplicateSteps(this); - - // Clear fields for files that turned out to not exist. -#define CLEAR_NOT_HAS(has_name, field_name) if(!has_name) { field_name= ""; } - CLEAR_NOT_HAS(m_bHasBanner, m_sBannerFile); - CLEAR_NOT_HAS(m_bHasBackground, m_sBackgroundFile); - CLEAR_NOT_HAS(has_jacket, m_sJacketFile); - CLEAR_NOT_HAS(has_cdimage, m_sCDFile); - CLEAR_NOT_HAS(has_disc, m_sDiscFile); - CLEAR_NOT_HAS(has_cdtitle, m_sCDTitleFile); -#undef CLEAR_NOT_HAS } /* Generate these before we autogen notes, so the new notes can inherit @@ -2140,6 +2200,11 @@ public: lua_pushboolean(L, p->GetEnabled()); return 1; } + static int IsCustomSong(T* p, lua_State* L) + { + lua_pushboolean(L, p->m_LoadedFromProfile != ProfileSlot_Invalid); + return 1; + } static int GetGroupName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sGroupName); @@ -2362,6 +2427,7 @@ public: ADD_METHOD( GetSongFilePath ); ADD_METHOD( IsTutorial ); ADD_METHOD( IsEnabled ); + ADD_METHOD(IsCustomSong); ADD_METHOD( GetGroupName ); ADD_METHOD( MusicLengthSeconds ); ADD_METHOD( GetSampleStart ); diff --git a/src/Song.h b/src/Song.h index 76a24d743e..428e6cf1a7 100644 --- a/src/Song.h +++ b/src/Song.h @@ -66,9 +66,11 @@ struct LyricSegment class Song { RString m_sSongDir; + RString m_pre_customify_song_dir; public: void SetSongDir( const RString sDir ) { m_sSongDir = sDir; } RString GetSongDir() { return m_sSongDir; } + RString GetPreCustomifyDir() { return m_pre_customify_song_dir; } /** @brief When should this song be displayed in the music wheel? */ enum SelectionDisplay @@ -87,7 +89,8 @@ public: * * This assumes that there is no song present right now. * @param sDir the song directory from which to load. */ - bool LoadFromSongDir( RString sDir, bool load_autosave= false ); + bool LoadFromSongDir(RString sDir, bool load_autosave= false, + ProfileSlot from_profile= ProfileSlot_Invalid); // This one takes the effort to reuse Steps pointers as best as it can bool ReloadFromSongDir( RString sDir ); bool ReloadFromSongDir() { return ReloadFromSongDir(GetSongDir()); } diff --git a/src/SongManager.cpp b/src/SongManager.cpp index 8cc018f33b..58bab18e53 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -60,6 +60,7 @@ static Preference g_bHideIncompleteCourses( "HideIncompleteCourses", false RString SONG_GROUP_COLOR_NAME( size_t i ) { return ssprintf( "SongGroupColor%i", (int) i+1 ); } RString COURSE_GROUP_COLOR_NAME( size_t i ) { return ssprintf( "CourseGroupColor%i", (int) i+1 ); } +RString profile_song_group_color_name(size_t i) { return ssprintf("ProfileSongGroupColor%i", (int)i+1); } static const float next_loading_window_update= 0.02f; @@ -78,6 +79,8 @@ SongManager::SongManager() SONG_GROUP_COLOR .Load( "SongManager", SONG_GROUP_COLOR_NAME, NUM_SONG_GROUP_COLORS ); NUM_COURSE_GROUP_COLORS .Load( "SongManager", "NumCourseGroupColors" ); COURSE_GROUP_COLOR .Load( "SongManager", COURSE_GROUP_COLOR_NAME, NUM_COURSE_GROUP_COLORS ); + num_profile_song_group_colors.Load("SongManager", "NumProfileSongGroupColors"); + profile_song_group_colors.Load("SongManager", profile_song_group_color_name, num_profile_song_group_colors); } SongManager::~SongManager() @@ -545,8 +548,19 @@ RageColor SongManager::GetSongGroupColor( const RString &sSongGroup ) const return SONG_GROUP_COLOR.GetValue( i%NUM_SONG_GROUP_COLORS ); } } + FOREACH_EnabledPlayer(pn) + { + Profile* prof= PROFILEMAN->GetProfile(pn); + if(prof != NULL) + { + if(prof->GetDisplayNameOrHighScoreName() == sSongGroup) + { + return profile_song_group_colors.GetValue(pn % num_profile_song_group_colors); + } + } + } - ASSERT_M( 0, ssprintf("requested color for song group '%s' that doesn't exist",sSongGroup.c_str()) ); + LuaHelpers::ReportScriptErrorFmt("requested color for song group '%s' that doesn't exist",sSongGroup.c_str()); return RageColor(1,1,1,1); } @@ -711,6 +725,17 @@ const vector &SongManager::GetSongs( const RString &sGroupName ) const map::const_iterator iter = m_mapSongGroupIndex.find( sGroupName ); if ( iter != m_mapSongGroupIndex.end() ) return iter->second; + FOREACH_EnabledPlayer(pn) + { + Profile* prof= PROFILEMAN->GetProfile(pn); + if(prof != NULL) + { + if(prof->GetDisplayNameOrHighScoreName() == sGroupName) + { + return prof->m_songs; + } + } + } return vEmpty; } diff --git a/src/SongManager.h b/src/SongManager.h index f7802193af..ffeba83e8d 100644 --- a/src/SongManager.h +++ b/src/SongManager.h @@ -223,6 +223,8 @@ protected: ThemeMetric1D SONG_GROUP_COLOR; ThemeMetric NUM_COURSE_GROUP_COLORS; ThemeMetric1D COURSE_GROUP_COLOR; + ThemeMetric num_profile_song_group_colors; + ThemeMetric1D profile_song_group_colors; }; extern SongManager* SONGMAN; // global and accessible from anywhere in our program diff --git a/src/Steps.cpp b/src/Steps.cpp index 5c24cd0c94..5ac2430820 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -351,6 +351,15 @@ void Steps::CalculateRadarValues( float fMusicLengthSeconds ) GAMESTATE->SetProcessedTimingData(NULL); } +void Steps::ChangeFilenamesForCustomSong() +{ + m_sFilename= custom_songify_path(m_sFilename); + if(!m_MusicFile.empty()) + { + m_MusicFile= custom_songify_path(m_MusicFile); + } +} + void Steps::Decompress() const { const_cast(this)->Decompress(); diff --git a/src/Steps.h b/src/Steps.h index ce2d9928a7..b80d9f3b86 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -132,6 +132,8 @@ public: RString GetChartKey(); void SetChartKey(const RString &k) { ChartKey = k; } + void ChangeFilenamesForCustomSong(); + void SetLoadedFromProfile( ProfileSlot slot ) { m_LoadedFromProfile = slot; } void SetMeter( int meter ); void SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] ); diff --git a/src/ThemeManager.cpp b/src/ThemeManager.cpp index 2f34e63640..97f4723f9a 100644 --- a/src/ThemeManager.cpp +++ b/src/ThemeManager.cpp @@ -459,7 +459,7 @@ void ThemeManager::SwitchThemeAndLanguage( const RString &sThemeName_, const RSt if( PROFILEMAN != NULL ) { Profile* pProfile = PROFILEMAN->GetMachineProfile(); - pProfile->LoadCustomFunction( "/Save/MachineProfile/" ); + pProfile->LoadCustomFunction("/Save/MachineProfile/", PlayerNumber_Invalid); } }