From 2aee27a276e0230ea0512c8ba184a80b84bf43f4 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 6 Jun 2017 06:30:06 -0600 Subject: [PATCH] Backport custom song support. Prefs are CustomSongsEnable, CustomSongsMaxCount, CustomSongsLoadTimeout, CustomSongsMaxSeconds, CustomSongsMaxMegabytes. Metrics are NumProfileSongGroupColors and the colors with it. Profile custom load function is passed PlayerNumber now. Stepmania must run as root to save USB profile scores. (#1470) --- Themes/_fallback/Languages/en.ini | 24 +- Themes/_fallback/metrics.ini | 3 + src/GameState.cpp | 63 ++++ src/GameState.h | 2 + src/LuaManager.cpp | 2 + src/MusicWheel.cpp | 13 + src/PrefsManager.cpp | 6 + src/PrefsManager.h | 6 + src/Profile.cpp | 107 +++++- src/Profile.h | 12 +- src/ProfileManager.cpp | 13 +- src/RageUtil.cpp | 13 + src/RageUtil.h | 3 + src/ScreenOptionsMasterPrefs.cpp | 28 ++ src/ScreenSelectMusic.cpp | 1 + src/Song.cpp | 524 +++++++++++++++++------------- src/Song.h | 5 +- src/SongManager.cpp | 27 +- src/SongManager.h | 2 + src/Steps.cpp | 9 + src/Steps.h | 2 + src/ThemeManager.cpp | 2 +- 22 files changed, 627 insertions(+), 240 deletions(-) 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); } }