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)

This commit is contained in:
Kyzentun
2017-06-06 05:30:06 -07:00
committed by Colby Klein
parent 1c920a4197
commit 2aee27a276
22 changed files with 627 additions and 240 deletions
+23 -1
View File
@@ -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
+3
View File
@@ -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]
+63
View File
@@ -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<RString> copy_exts= ActorUtil::GetTypeExtensionList(FT_Sound);
copy_exts.push_back("sm");
copy_exts.push_back("ssc");
copy_exts.push_back("lrc");
vector<RString> 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);
}
};
+2
View File
@@ -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.
+2
View File
@@ -47,6 +47,7 @@ namespace LuaHelpers
template<> bool FromStack<bool>( Lua *L, bool &Object, int iOffset );
template<> bool FromStack<float>( Lua *L, float &Object, int iOffset );
template<> bool FromStack<int>( Lua *L, int &Object, int iOffset );
template<> bool FromStack<unsigned int>( Lua *L, unsigned int &Object, int iOffset );
template<> bool FromStack<RString>( Lua *L, RString &Object, int iOffset );
bool InReportScriptError= false;
@@ -88,6 +89,7 @@ namespace LuaHelpers
template<> bool FromStack<bool>( Lua *L, bool &Object, int iOffset ) { Object = !!lua_toboolean( L, iOffset ); return true; }
template<> bool FromStack<float>( Lua *L, float &Object, int iOffset ) { Object = (float)lua_tonumber( L, iOffset ); return true; }
template<> bool FromStack<int>( Lua *L, int &Object, int iOffset ) { Object = lua_tointeger( L, iOffset ); return true; }
template<> bool FromStack<unsigned int>( Lua *L, unsigned int &Object, int iOffset ) { Object = lua_tointeger( L, iOffset ); return true; }
template<> bool FromStack<RString>( Lua *L, RString &Object, int iOffset )
{
size_t iLen;
+13
View File
@@ -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<Song*> &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<Song*> vTempSongs;
+6
View File
@@ -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)
+6
View File
@@ -309,6 +309,12 @@ public:
/** @brief Enable some quirky behavior used by some older versions of StepMania. */
Preference<bool> m_bQuirksMode;
Preference<bool> m_custom_songs_enable;
Preference<unsigned int> m_custom_songs_max_count;
Preference<float> m_custom_songs_load_timeout;
Preference<float> m_custom_songs_max_seconds;
Preference<float> m_custom_songs_max_megabytes;
// Debug:
Preference<bool> m_bLogToDisk;
Preference<bool> m_bForceLogFlush;
+103 -4
View File
@@ -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<RString> 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);
}
};
+10 -2
View File
@@ -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<RString,RString> m_sDefaultModifiers;
SortOrder m_SortOrder;
std::vector<Song*> 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 );
+12 -1
View File
@@ -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;
}
+13
View File
@@ -18,6 +18,8 @@
#include <sys/stat.h>
#include <math.h>
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<RString> 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" */
+3
View File
@@ -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 <algorithm>. */
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 );
+28
View File
@@ -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<ConfOption> g_ConfOptions;
static void InitializeConfOptions()
{
@@ -808,6 +831,11 @@ static void InitializeConfOptions()
ADD( ConfOption( "PickExtraStage", MovePref<bool>, "Off","On" ) );
ADD( ConfOption( "UseUnlockSystem", MovePref<bool>, "Off","On" ) );
ADD( ConfOption( "AllowSongDeletion", MovePref<bool>, "Off","On" ) );
ADD(ConfOption("CustomSongsEnable", MovePref<bool>, "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<bool>, "Off","On" ) );
+1
View File
@@ -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
+295 -229
View File
@@ -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<RString> 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<RString> 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<RString> 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<RString> vs;
GetDirListing( sDir + "*.mp3", vs, false, false );
GetDirListing( sDir + "*.oga", vs, false, false );
GetDirListing( sDir + "*.ogg", vs, false, false );
bool bHasMusic = !vs.empty();
vector<RString> 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<RString>::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<RString> 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<RString> contains(1, "banner");
/* Some people do things differently for the sake of being different.
* Don't match eg. abnormal, numbness. */
vector<RString> 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<RString> contains(1, "background");
vector<RString> 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<RString> starts_with(1, "jk_");
vector<RString> 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<RString> 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<RString> 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<RString> 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<RString> 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<RString> contains(1, "banner");
/* Some people do things differently for the sake of being different.
* Don't match eg. abnormal, numbness. */
vector<RString> 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<RString> contains(1, "background");
vector<RString> 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<RString> starts_with(1, "jk_");
vector<RString> 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<RString> 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<RString> 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<RString> 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 );
+4 -1
View File
@@ -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()); }
+26 -1
View File
@@ -60,6 +60,7 @@ static Preference<bool> 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<Song*> &SongManager::GetSongs( const RString &sGroupName ) const
map<RString, SongPointerVector, Comp>::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;
}
+2
View File
@@ -223,6 +223,8 @@ protected:
ThemeMetric1D<RageColor> SONG_GROUP_COLOR;
ThemeMetric<int> NUM_COURSE_GROUP_COLORS;
ThemeMetric1D<RageColor> COURSE_GROUP_COLOR;
ThemeMetric<int> num_profile_song_group_colors;
ThemeMetric1D<RageColor> profile_song_group_colors;
};
extern SongManager* SONGMAN; // global and accessible from anywhere in our program
+9
View File
@@ -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<Steps *>(this)->Decompress();
+2
View File
@@ -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] );
+1 -1
View File
@@ -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);
}
}