2012-06-27 20:25:59 -05:00
# include "global.h"
# include "SongManager.h"
# include "arch/LoadingWindow/LoadingWindow.h"
2015-03-30 17:45:52 -06:00
# include "ActorUtil.h"
2012-06-27 20:25:59 -05:00
# include "AnnouncerManager.h"
# include "BackgroundUtil.h"
2017-09-19 23:49:42 +02:00
# include "ImageCache.h"
2012-06-27 20:25:59 -05:00
# include "CommonMetrics.h"
# include "Course.h"
# include "CourseLoaderCRS.h"
# include "CourseUtil.h"
# include "GameManager.h"
# include "GameState.h"
# include "LocalizedString.h"
2022-03-18 21:55:07 +01:00
# include "MemoryCardManager.h"
2012-06-27 20:25:59 -05:00
# include "MsdFile.h"
# include "NoteSkinManager.h"
# include "NotesLoaderDWI.h"
# include "NotesLoaderSSC.h"
# include "NotesLoaderSM.h"
# include "PrefsManager.h"
# include "Profile.h"
# include "ProfileManager.h"
# include "RageFile.h"
# include "RageFileManager.h"
# include "RageLog.h"
# include "Song.h"
2015-03-30 17:45:52 -06:00
# include "SongCacheIndex.h"
2012-06-27 20:25:59 -05:00
# include "SongUtil.h"
# include "Sprite.h"
# include "StatsManager.h"
# include "Steps.h"
# include "StepsUtil.h"
# include "Style.h"
# include "ThemeManager.h"
# include "TitleSubstitution.h"
# include "TrailUtil.h"
# include "UnlockManager.h"
# include "SpecialFiles.h"
2022-03-18 21:55:07 +01:00
# include <tuple>
2019-06-22 12:35:38 -07:00
SongManager * SONGMAN = nullptr ; // global and accessible from anywhere in our program
2012-06-27 20:25:59 -05:00
/** @brief The file that contains various random attacks. */
const RString ATTACK_FILE = " /Data/RandomAttacks.txt " ;
2022-02-19 22:07:36 +01:00
const RString EDIT_SUBDIR = " Edits/ " ;
2012-06-27 20:25:59 -05:00
static const ThemeMetric < RageColor > EXTRA_COLOR ( " SongManager " , " ExtraColor " ) ;
static const ThemeMetric < int > EXTRA_COLOR_METER ( " SongManager " , " ExtraColorMeter " ) ;
static const ThemeMetric < bool > USE_PREFERRED_SORT_COLOR ( " SongManager " , " UsePreferredSortColor " ) ;
static const ThemeMetric < bool > USE_UNLOCK_COLOR ( " SongManager " , " UseUnlockColor " ) ;
static const ThemeMetric < RageColor > UNLOCK_COLOR ( " SongManager " , " UnlockColor " ) ;
static const ThemeMetric < bool > MOVE_UNLOCKS_TO_BOTTOM_OF_PREFERRED_SORT ( " SongManager " , " MoveUnlocksToBottomOfPreferredSort " ) ;
static const ThemeMetric < int > EXTRA_STAGE2_DIFFICULTY_MAX ( " SongManager " , " ExtraStage2DifficultyMax " ) ;
static Preference < RString > g_sDisabledSongs ( " DisabledSongs " , " " ) ;
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 ) ; }
2017-06-06 06:30:06 -06:00
RString profile_song_group_color_name ( size_t i ) { return ssprintf ( " ProfileSongGroupColor%i " , ( int ) i + 1 ) ; }
2012-06-27 20:25:59 -05:00
2016-04-18 13:08:20 -06:00
static const float next_loading_window_update = 0.02f ;
2012-06-27 20:25:59 -05:00
SongManager : : SongManager ( )
{
// Register with Lua.
{
Lua * L = LUA - > Get ( ) ;
lua_pushstring ( L , " SONGMAN " ) ;
this - > PushSelf ( L ) ;
lua_settable ( L , LUA_GLOBALSINDEX ) ;
LUA - > Release ( L ) ;
}
NUM_SONG_GROUP_COLORS . Load ( " SongManager " , " NumSongGroupColors " ) ;
2022-06-09 11:59:40 +02:00
SONG_GROUP_COLOR . Load ( " SongManager " , SONG_GROUP_COLOR_NAME , NUM_SONG_GROUP_COLORS ) ;
2012-06-27 20:25:59 -05:00
NUM_COURSE_GROUP_COLORS . Load ( " SongManager " , " NumCourseGroupColors " ) ;
2022-06-09 11:59:40 +02:00
COURSE_GROUP_COLOR . Load ( " SongManager " , COURSE_GROUP_COLOR_NAME , NUM_COURSE_GROUP_COLORS ) ;
2017-06-06 06:30:06 -06:00
num_profile_song_group_colors . Load ( " SongManager " , " NumProfileSongGroupColors " ) ;
profile_song_group_colors . Load ( " SongManager " , profile_song_group_color_name , num_profile_song_group_colors ) ;
2012-06-27 20:25:59 -05:00
}
SongManager : : ~ SongManager ( )
{
// Unregister with Lua.
LUA - > UnsetGlobal ( " SONGMAN " ) ;
// Courses depend on Songs and Songs don't depend on Courses.
// So, delete the Courses first.
FreeCourses ( ) ;
FreeSongs ( ) ;
}
2022-06-09 15:31:29 +02:00
void SongManager : : InitAll ( LoadingWindow * ld , bool onlyAdditions )
2012-06-27 20:25:59 -05:00
{
2022-07-10 18:28:56 +03:00
std : : vector < RString > never_cache ;
2015-03-31 15:20:37 -06:00
split ( PREFSMAN - > m_NeverCacheList , " , " , never_cache ) ;
2022-07-10 18:28:56 +03:00
for ( std : : vector < RString > : : iterator group = never_cache . begin ( ) ;
2015-03-31 15:20:37 -06:00
group ! = never_cache . end ( ) ; + + group )
{
m_GroupsToNeverCache . insert ( * group ) ;
}
2022-06-09 15:31:29 +02:00
InitSongsFromDisk ( ld , onlyAdditions ) ;
InitCoursesFromDisk ( ld , onlyAdditions ) ;
if ( onlyAdditions )
{
DeleteAutogenCourses ( ) ;
}
2012-06-27 20:25:59 -05:00
InitAutogenCourses ( ) ;
InitRandomAttacks ( ) ;
}
static LocalizedString RELOADING ( " SongManager " , " Reloading... " ) ;
static LocalizedString UNLOADING_SONGS ( " SongManager " , " Unloading songs... " ) ;
static LocalizedString UNLOADING_COURSES ( " SongManager " , " Unloading courses... " ) ;
2015-05-06 15:48:23 -06:00
static LocalizedString SANITY_CHECKING_GROUPS ( " SongManager " , " Sanity checking groups... " ) ;
2012-06-27 20:25:59 -05:00
2022-10-28 17:17:06 +02:00
void SongManager : : Reload ( bool bAllowFastLoad , LoadingWindow * ld )
2012-06-27 20:25:59 -05:00
{
FILEMAN - > FlushDirCache ( SpecialFiles : : SONGS_DIR ) ;
FILEMAN - > FlushDirCache ( SpecialFiles : : COURSES_DIR ) ;
FILEMAN - > FlushDirCache ( EDIT_SUBDIR ) ;
if ( ld )
ld - > SetText ( RELOADING ) ;
// save scores before unloading songs, or the scores will be lost
PROFILEMAN - > SaveMachineProfile ( ) ;
2022-03-18 21:55:07 +01:00
GAMESTATE - > SavePlayerProfiles ( ) ;
std : : vector < std : : tuple < PlayerNumber , RString , bool > > rejoinPlayers ;
FOREACH_HumanPlayer ( pn )
{
if ( GAMESTATE - > m_bSideIsJoined [ pn ] )
{
if ( PROFILEMAN - > ProfileWasLoadedFromMemoryCard ( pn ) )
{
rejoinPlayers . push_back ( std : : make_tuple ( pn , RString ( " " ) , true ) ) ;
}
else if ( PROFILEMAN - > IsPersistentProfile ( pn ) )
{
int numLocalProfiles = PROFILEMAN - > GetNumLocalProfiles ( ) ;
for ( int i = 0 ; i < numLocalProfiles ; i + + )
{
Profile * profile = PROFILEMAN - > GetLocalProfileFromIndex ( i ) ;
if ( profile - > m_sGuid = = PROFILEMAN - > GetProfile ( pn ) - > m_sGuid )
{
RString profileID = PROFILEMAN - > GetLocalProfileIDFromIndex ( i ) ;
rejoinPlayers . push_back ( std : : make_tuple ( pn , profileID , false ) ) ;
break ;
}
}
}
else
{
rejoinPlayers . push_back ( std : : make_tuple ( pn , RString ( " " ) , false ) ) ;
}
GAMESTATE - > UnjoinPlayer ( pn ) ;
PROFILEMAN - > UnloadProfile ( pn ) ;
}
}
2012-06-27 20:25:59 -05:00
if ( ld )
ld - > SetText ( UNLOADING_COURSES ) ;
FreeCourses ( ) ;
if ( ld )
ld - > SetText ( UNLOADING_SONGS ) ;
FreeSongs ( ) ;
2022-10-28 17:17:06 +02:00
const bool oldVal = PREFSMAN - > m_bFastLoad ;
if ( ! bAllowFastLoad )
PREFSMAN - > m_bFastLoad . Set ( false ) ;
2022-06-09 15:31:29 +02:00
InitAll ( ld , /*onlyAdditions=*/ false ) ;
2012-06-27 20:25:59 -05:00
// reload scores and unlocks afterward
PROFILEMAN - > LoadMachineProfile ( ) ;
2022-03-18 21:55:07 +01:00
MEMCARDMAN - > WaitForCheckingToComplete ( ) ;
for ( auto & it : rejoinPlayers )
{
PlayerNumber pn ;
RString profileID ;
bool isMemoryCard ;
std : : tie ( pn , profileID , isMemoryCard ) = it ;
GAMESTATE - > JoinPlayer ( pn ) ;
if ( isMemoryCard )
{
bool success = MEMCARDMAN - > MountCard ( pn ) ;
if ( success )
{
PROFILEMAN - > LoadProfileFromMemoryCard ( pn , true ) ;
MEMCARDMAN - > UnmountCard ( pn ) ;
}
}
else
{
PROFILEMAN - > m_sDefaultLocalProfileID [ pn ] . Set ( profileID ) ;
PROFILEMAN - > LoadLocalProfileFromMachine ( pn ) ;
}
GAMESTATE - > LoadCurrentSettingsFromProfile ( pn ) ;
}
2012-06-27 20:25:59 -05:00
UNLOCKMAN - > Reload ( ) ;
2022-10-28 17:17:06 +02:00
if ( ! bAllowFastLoad )
PREFSMAN - > m_bFastLoad . Set ( oldVal ) ;
2012-06-27 20:25:59 -05:00
UpdatePreferredSort ( ) ;
}
2022-06-09 15:31:29 +02:00
void SongManager : : LoadAdditions ( LoadingWindow * ld )
{
FILEMAN - > FlushDirCache ( SpecialFiles : : SONGS_DIR ) ;
FILEMAN - > FlushDirCache ( SpecialFiles : : COURSES_DIR ) ;
FILEMAN - > FlushDirCache ( EDIT_SUBDIR ) ;
InitAll ( ld , /*onlyAdditions=*/ true ) ;
UNLOCKMAN - > Reload ( ) ;
UpdatePreferredSort ( ) ;
}
void SongManager : : InitSongsFromDisk ( LoadingWindow * ld , bool onlyAdditions )
2012-06-27 20:25:59 -05:00
{
RageTimer tm ;
2015-03-30 17:45:52 -06:00
// Tell SONGINDEX to not write the cache index file every time a song adds
// an entry. -Kyz
2019-10-12 11:24:13 -04:00
SONGINDEX - > delay_save_cache = true ;
IMAGECACHE - > delay_save_cache = true ;
2022-06-09 15:31:29 +02:00
LoadSongDir ( SpecialFiles : : SONGS_DIR , ld , onlyAdditions ) ;
2015-03-30 17:45:52 -06:00
LoadEnabledSongsFromPref ( ) ;
SONGINDEX - > SaveCacheIndex ( ) ;
2022-06-09 15:31:29 +02:00
SONGINDEX - > delay_save_cache = false ;
2019-10-12 11:24:13 -04:00
IMAGECACHE - > WriteToDisk ( ) ;
IMAGECACHE - > delay_save_cache = false ;
2012-06-27 20:25:59 -05:00
LOG - > Trace ( " Found %d songs in %f seconds. " , ( int ) m_pSongs . size ( ) , tm . GetDeltaTime ( ) ) ;
}
static LocalizedString FOLDER_CONTAINS_MUSIC_FILES ( " SongManager " , " The folder \" %s \" appears to be a song folder. All song folders must reside in a group folder. For example, \" Songs/Originals/My Song \" . " ) ;
void SongManager : : SanityCheckGroupDir ( RString sDir ) const
{
// Check to see if they put a song directly inside the group folder.
2022-07-10 18:28:56 +03:00
std : : vector < RString > arrayFiles ;
2015-03-30 17:45:52 -06:00
GetDirListing ( sDir + " /* " , arrayFiles ) ;
2022-07-10 18:28:56 +03:00
const std : : vector < RString > & audio_exts = ActorUtil : : GetTypeExtensionList ( FT_Sound ) ;
2019-06-22 12:35:38 -07:00
for ( RString & fname : arrayFiles )
2015-03-30 17:45:52 -06:00
{
2019-06-22 12:35:38 -07:00
const RString ext = GetExtension ( fname ) ;
for ( RString const & aud : audio_exts )
2015-03-30 17:45:52 -06:00
{
2019-06-22 12:35:38 -07:00
if ( ext = = aud )
2015-03-30 17:45:52 -06:00
{
RageException : : Throw (
FOLDER_CONTAINS_MUSIC_FILES . GetValue ( ) , sDir . c_str ( ) ) ;
}
}
}
2012-06-27 20:25:59 -05:00
}
void SongManager : : AddGroup ( RString sDir , RString sGroupDirName )
{
unsigned j ;
for ( j = 0 ; j < m_sSongGroupNames . size ( ) ; + + j )
if ( sGroupDirName = = m_sSongGroupNames [ j ] )
break ;
if ( j ! = m_sSongGroupNames . size ( ) )
return ; // the group is already added
// Look for a group banner in this group folder
2022-07-10 18:28:56 +03:00
std : : vector < RString > arrayGroupBanners ;
2012-06-27 20:25:59 -05:00
GetDirListing ( sDir + sGroupDirName + " /*.png " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " /*.jpg " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " /*.jpeg " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " /*.gif " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " /*.bmp " , arrayGroupBanners ) ;
RString sBannerPath ;
if ( ! arrayGroupBanners . empty ( ) )
sBannerPath = sDir + sGroupDirName + " / " + arrayGroupBanners [ 0 ] ;
else
{
// Look for a group banner in the parent folder
GetDirListing ( sDir + sGroupDirName + " .png " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " .jpg " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " .jpeg " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " .gif " , arrayGroupBanners ) ;
GetDirListing ( sDir + sGroupDirName + " .bmp " , arrayGroupBanners ) ;
if ( ! arrayGroupBanners . empty ( ) )
sBannerPath = sDir + arrayGroupBanners [ 0 ] ;
}
/* Other group graphics are a bit trickier, and usually don't exist.
* A themer has a few options, namely checking the aspect ratio and
* operating on it. -aj
* TODO: Once the files are implemented in Song, bring the extensions
* from there into here. -aj */
// Group background
//vector<RString> arrayGroupBackgrounds;
//GetDirListing( sDir+sGroupDirName+"/*-bg.png", arrayGroupBanners );
//GetDirListing( sDir+sGroupDirName+"/*-bg.jpg", arrayGroupBanners );
//GetDirListing( sDir+sGroupDirName+"/*-bg.jpeg", arrayGroupBanners );
//GetDirListing( sDir+sGroupDirName+"/*-bg.gif", arrayGroupBanners );
//GetDirListing( sDir+sGroupDirName+"/*-bg.bmp", arrayGroupBanners );
/*
RString sBackgroundPath;
if( !arrayGroupBackgrounds.empty() )
sBackgroundPath = sDir+sGroupDirName+"/"+arrayGroupBackgrounds[0];
else
{
// Look for a group background in the parent folder
GetDirListing( sDir+sGroupDirName+"-bg.png", arrayGroupBackgrounds );
GetDirListing( sDir+sGroupDirName+"-bg.jpg", arrayGroupBackgrounds );
GetDirListing( sDir+sGroupDirName+"-bg.jpeg", arrayGroupBackgrounds );
GetDirListing( sDir+sGroupDirName+"-bg.gif", arrayGroupBackgrounds );
GetDirListing( sDir+sGroupDirName+"-bg.bmp", arrayGroupBackgrounds );
if( !arrayGroupBackgrounds.empty() )
sBackgroundPath = sDir+arrayGroupBackgrounds[0];
}
*/
/*
LOG->Trace( "Group banner for '%s' is '%s'.", sGroupDirName.c_str(),
sBannerPath != ""? sBannerPath.c_str():"(none)" );
*/
m_sSongGroupNames . push_back ( sGroupDirName ) ;
m_sSongGroupBannerPaths . push_back ( sBannerPath ) ;
//m_sSongGroupBackgroundPaths.push_back( sBackgroundPath );
}
static LocalizedString LOADING_SONGS ( " SongManager " , " Loading songs... " ) ;
2022-06-09 15:31:29 +02:00
void SongManager : : LoadSongDir ( RString sDir , LoadingWindow * ld , bool onlyAdditions )
2012-06-27 20:25:59 -05:00
{
2022-06-09 15:31:29 +02:00
if ( ld )
ld - > SetText ( LOADING_SONGS ) ;
2016-04-18 13:08:20 -06:00
// Compositors and other stuff can impose some overhead on updating the
// loading window, which slows down startup time for some people.
// loading_window_last_update_time provides a timer so the loading window
// isn't updated after every song and course. -Kyz
RageTimer loading_window_last_update_time ;
loading_window_last_update_time . Touch ( ) ;
2012-06-27 20:25:59 -05:00
// Make sure sDir has a trailing slash.
if ( sDir . Right ( 1 ) ! = " / " )
sDir + = " / " ;
// Find all group directories in "Songs" folder
2022-07-10 18:28:56 +03:00
std : : vector < RString > arrayGroupDirs ;
2012-06-27 20:25:59 -05:00
GetDirListing ( sDir + " * " , arrayGroupDirs , true ) ;
SortRStringArray ( arrayGroupDirs ) ;
StripCvsAndSvn ( arrayGroupDirs ) ;
StripMacResourceForks ( arrayGroupDirs ) ;
2022-07-10 18:28:56 +03:00
std : : vector < std : : vector < RString > > arrayGroupSongDirs ;
2012-06-27 20:25:59 -05:00
int groupIndex , songCount , songIndex ;
groupIndex = 0 ;
songCount = 0 ;
2015-05-06 15:48:23 -06:00
if ( ld )
{
ld - > SetIndeterminate ( false ) ;
ld - > SetTotalWork ( arrayGroupDirs . size ( ) ) ;
}
int sanity_index = 0 ;
2019-06-22 12:35:38 -07:00
for ( RString const & sGroupDirName : arrayGroupDirs ) // foreach dir in /Songs/
2012-06-27 20:25:59 -05:00
{
2016-04-18 13:08:20 -06:00
if ( ld & & loading_window_last_update_time . Ago ( ) > next_loading_window_update )
2015-05-06 15:48:23 -06:00
{
2016-04-18 13:08:20 -06:00
loading_window_last_update_time . Touch ( ) ;
2015-05-06 15:48:23 -06:00
ld - > SetProgress ( sanity_index ) ;
ld - > SetText ( SANITY_CHECKING_GROUPS . GetValue ( ) + ssprintf ( " \n %s " ,
Basename ( sGroupDirName ) . c_str ( ) ) ) ;
}
2012-06-27 20:25:59 -05:00
// TODO: If this check fails, log a warning instead of crashing.
SanityCheckGroupDir ( sDir + sGroupDirName ) ;
// Find all Song folders in this group directory
2022-07-10 18:28:56 +03:00
std : : vector < RString > arraySongDirs ;
2012-06-27 20:25:59 -05:00
GetDirListing ( sDir + sGroupDirName + " /* " , arraySongDirs , true , true ) ;
StripCvsAndSvn ( arraySongDirs ) ;
StripMacResourceForks ( arraySongDirs ) ;
SortRStringArray ( arraySongDirs ) ;
arrayGroupSongDirs . push_back ( arraySongDirs ) ;
songCount + = arraySongDirs . size ( ) ;
}
if ( songCount = = 0 ) return ;
if ( ld ) {
ld - > SetIndeterminate ( false ) ;
ld - > SetTotalWork ( songCount ) ;
}
groupIndex = 0 ;
songIndex = 0 ;
2019-06-22 12:35:38 -07:00
for ( RString const & sGroupDirName : arrayGroupDirs ) // foreach dir in /Songs/
2012-06-27 20:25:59 -05:00
{
2022-07-10 18:28:56 +03:00
std : : vector < RString > & arraySongDirs = arrayGroupSongDirs [ groupIndex + + ] ;
2012-06-27 20:25:59 -05:00
LOG - > Trace ( " Attempting to load %i songs from \" %s \" " , int ( arraySongDirs . size ( ) ) ,
( sDir + sGroupDirName ) . c_str ( ) ) ;
int loaded = 0 ;
SongPointerVector & index_entry = m_mapSongGroupIndex [ sGroupDirName ] ;
2016-04-18 13:08:20 -06:00
RString group_base_name = Basename ( sGroupDirName ) ;
2012-06-27 20:25:59 -05:00
for ( unsigned j = 0 ; j < arraySongDirs . size ( ) ; + + j ) // for each song dir
{
RString sSongDirName = arraySongDirs [ j ] ;
2022-06-09 15:31:29 +02:00
// Skip already loaded songs if onlyAdditions is set.
if ( onlyAdditions )
{
SongID songID ;
songID . FromString ( sSongDirName ) ;
if ( songID . ToSong ( ) ! = nullptr )
continue ;
}
2012-06-27 20:25:59 -05:00
// this is a song directory. Load a new song.
2016-04-18 13:08:20 -06:00
if ( ld & & loading_window_last_update_time . Ago ( ) > next_loading_window_update )
2012-06-27 20:25:59 -05:00
{
2016-04-18 13:08:20 -06:00
loading_window_last_update_time . Touch ( ) ;
2012-06-27 20:25:59 -05:00
ld - > SetProgress ( songIndex ) ;
ld - > SetText ( LOADING_SONGS . GetValue ( ) +
ssprintf ( " \n %s \n %s " ,
2016-04-18 13:08:20 -06:00
group_base_name . c_str ( ) ,
2012-06-27 20:25:59 -05:00
Basename ( sSongDirName ) . c_str ( )
)
) ;
}
2022-06-09 15:31:29 +02:00
2012-06-27 20:25:59 -05:00
Song * pNewSong = new Song ;
if ( ! pNewSong - > LoadFromSongDir ( sSongDirName ) )
{
// The song failed to load.
delete pNewSong ;
continue ;
}
2015-03-30 17:45:52 -06:00
AddSongToList ( pNewSong ) ;
2012-06-27 20:25:59 -05:00
index_entry . push_back ( pNewSong ) ;
loaded + + ;
songIndex + + ;
}
LOG - > Trace ( " Loaded %i songs from \" %s \" " , loaded , ( sDir + sGroupDirName ) . c_str ( ) ) ;
// Don't add the group name if we didn't load any songs in this group.
if ( ! loaded ) continue ;
// Add this group to the group array.
AddGroup ( sDir , sGroupDirName ) ;
// Cache and load the group banner. (and background if it has one -aj)
2017-09-19 23:49:42 +02:00
IMAGECACHE - > CacheImage ( " Banner " , GetSongGroupBannerPath ( sGroupDirName ) ) ;
2012-06-27 20:25:59 -05:00
// Load the group sym links (if any)
LoadGroupSymLinks ( sDir , sGroupDirName ) ;
}
if ( ld ) {
ld - > SetIndeterminate ( true ) ;
}
}
// Instead of "symlinks", songs should have membership in multiple groups. -Chris
void SongManager : : LoadGroupSymLinks ( RString sDir , RString sGroupFolder )
{
// Find all symlink files in this folder
2022-07-10 18:28:56 +03:00
std : : vector < RString > arraySymLinks ;
2012-06-27 20:25:59 -05:00
GetDirListing ( sDir + sGroupFolder + " /*.include " , arraySymLinks , false ) ;
SortRStringArray ( arraySymLinks ) ;
SongPointerVector & index_entry = m_mapSongGroupIndex [ sGroupFolder ] ;
for ( unsigned s = 0 ; s < arraySymLinks . size ( ) ; s + + ) // for each symlink in this dir, add it in as a song.
{
MsdFile msdF ;
msdF . ReadFile ( sDir + sGroupFolder + " / " + arraySymLinks [ s ] . c_str ( ) , false ) ; // don't unescape
RString sSymDestination = msdF . GetParam ( 0 , 1 ) ; // Should only be 1 value & param...period.
Song * pNewSong = new Song ;
if ( ! pNewSong - > LoadFromSongDir ( sSymDestination ) )
{
delete pNewSong ; // The song failed to load.
}
else
{
2022-07-10 18:28:56 +03:00
const std : : vector < Steps * > & vpSteps = pNewSong - > GetAllSteps ( ) ;
2012-06-27 20:25:59 -05:00
while ( vpSteps . size ( ) )
pNewSong - > DeleteSteps ( vpSteps [ 0 ] ) ;
FOREACH_BackgroundLayer ( i )
pNewSong - > GetBackgroundChanges ( i ) . clear ( ) ;
pNewSong - > m_bIsSymLink = true ; // Very important so we don't double-parse later
pNewSong - > m_sGroupName = sGroupFolder ;
2015-03-30 17:45:52 -06:00
AddSongToList ( pNewSong ) ;
2012-06-27 20:25:59 -05:00
index_entry . push_back ( pNewSong ) ;
}
}
}
void SongManager : : PreloadSongImages ( )
{
2017-09-19 23:49:42 +02:00
if ( PREFSMAN - > m_ImageCache ! = IMGCACHE_FULL )
2012-06-27 20:25:59 -05:00
return ;
/* Load textures before unloading old ones, so we don't reload textures
* that we don't need to. */
RageTexturePreloader preload ;
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & songs = GetAllSongs ( ) ;
2012-06-27 20:25:59 -05:00
for ( unsigned i = 0 ; i < songs . size ( ) ; + + i )
{
if ( ! songs [ i ] - > HasBanner ( ) )
continue ;
const RageTextureID ID = Sprite : : SongBannerTexture ( songs [ i ] - > GetBannerPath ( ) ) ;
preload . Load ( ID ) ;
}
2022-07-10 18:28:56 +03:00
std : : vector < Course * > courses ;
2012-06-27 20:25:59 -05:00
GetAllCourses ( courses , false ) ;
for ( unsigned i = 0 ; i < courses . size ( ) ; + + i )
{
if ( ! courses [ i ] - > HasBanner ( ) )
continue ;
const RageTextureID ID = Sprite : : SongBannerTexture ( courses [ i ] - > GetBannerPath ( ) ) ;
preload . Load ( ID ) ;
}
preload . Swap ( m_TexturePreload ) ;
}
void SongManager : : FreeSongs ( )
{
m_sSongGroupNames . clear ( ) ;
m_sSongGroupBannerPaths . clear ( ) ;
//m_sSongGroupBackgroundPaths.clear();
2013-05-05 07:34:04 -04:00
for ( Song * song : m_pSongs )
2013-05-05 08:22:45 -04:00
{
2013-05-05 07:34:04 -04:00
SAFE_DELETE ( song ) ;
2013-05-05 08:22:45 -04:00
}
2012-06-27 20:25:59 -05:00
m_pSongs . clear ( ) ;
2015-03-30 17:45:52 -06:00
m_SongsByDir . clear ( ) ;
2012-06-27 20:25:59 -05:00
2015-03-17 04:20:02 +00:00
// also free the songs that have been deleted from disk
for ( unsigned i = 0 ; i < m_pDeletedSongs . size ( ) ; + + i )
SAFE_DELETE ( m_pDeletedSongs [ i ] ) ;
m_pDeletedSongs . clear ( ) ;
m_mapSongGroupIndex . clear ( ) ;
2012-06-27 20:25:59 -05:00
m_sSongGroupBannerPaths . clear ( ) ;
m_pPopularSongs . clear ( ) ;
m_pShuffledSongs . clear ( ) ;
}
2015-03-17 04:20:02 +00:00
void SongManager : : UnlistSong ( Song * song )
{
// cannot immediately free song data, as it is needed temporarily for smooth audio transitions, etc.
// Instead, remove it from the m_pSongs list and store it in a special place where it can safely be deleted later.
m_pDeletedSongs . push_back ( song ) ;
// remove all occurences of the song in each of our song vectors
2022-07-10 18:28:56 +03:00
std : : vector < Song * > * songVectors [ 3 ] = { & m_pSongs , & m_pPopularSongs , & m_pShuffledSongs } ;
2015-03-17 04:20:02 +00:00
for ( int songVecIdx = 0 ; songVecIdx < 3 ; + + songVecIdx ) {
2022-12-23 12:34:02 -07:00
std : : vector < Song * > & v = * songVectors [ songVecIdx ] ;
2022-11-08 06:59:56 -07:00
std : : erase_if ( v , [ song ] ( Song * vSong ) { return vSong = = song ; } ) ;
2015-03-17 04:20:02 +00:00
}
}
2015-03-31 15:20:37 -06:00
bool SongManager : : IsGroupNeverCached ( const RString & group ) const
{
return m_GroupsToNeverCache . find ( group ) ! = m_GroupsToNeverCache . end ( ) ;
}
2012-06-27 20:25:59 -05:00
RString SongManager : : GetSongGroupBannerPath ( RString sSongGroup ) const
{
for ( unsigned i = 0 ; i < m_sSongGroupNames . size ( ) ; + + i )
{
if ( sSongGroup = = m_sSongGroupNames [ i ] )
return m_sSongGroupBannerPaths [ i ] ;
}
return RString ( ) ;
}
/*
RString SongManager::GetSongGroupBackgroundPath( RString sSongGroup ) const
{
for( unsigned i = 0; i < m_sSongGroupNames.size(); ++i )
{
if( sSongGroup == m_sSongGroupNames[i] )
return m_sSongGroupBackgroundPaths[i];
}
return RString();
}
*/
2022-07-10 18:28:56 +03:00
void SongManager : : GetSongGroupNames ( std : : vector < RString > & AddTo ) const
2012-06-27 20:25:59 -05:00
{
AddTo . insert ( AddTo . end ( ) , m_sSongGroupNames . begin ( ) , m_sSongGroupNames . end ( ) ) ;
}
bool SongManager : : DoesSongGroupExist ( RString sSongGroup ) const
{
return find ( m_sSongGroupNames . begin ( ) , m_sSongGroupNames . end ( ) , sSongGroup ) ! = m_sSongGroupNames . end ( ) ;
}
RageColor SongManager : : GetSongGroupColor ( const RString & sSongGroup ) const
{
for ( unsigned i = 0 ; i < m_sSongGroupNames . size ( ) ; i + + )
{
if ( m_sSongGroupNames [ i ] = = sSongGroup )
{
return SONG_GROUP_COLOR . GetValue ( i % NUM_SONG_GROUP_COLORS ) ;
}
}
2017-06-06 06:30:06 -06:00
FOREACH_EnabledPlayer ( pn )
{
Profile * prof = PROFILEMAN - > GetProfile ( pn ) ;
2019-06-22 12:35:38 -07:00
if ( prof ! = nullptr )
2017-06-06 06:30:06 -06:00
{
if ( prof - > GetDisplayNameOrHighScoreName ( ) = = sSongGroup )
{
return profile_song_group_colors . GetValue ( pn % num_profile_song_group_colors ) ;
}
}
}
2012-06-27 20:25:59 -05:00
2017-06-06 06:30:06 -06:00
LuaHelpers : : ReportScriptErrorFmt ( " requested color for song group '%s' that doesn't exist " , sSongGroup . c_str ( ) ) ;
2012-06-27 20:25:59 -05:00
return RageColor ( 1 , 1 , 1 , 1 ) ;
}
RageColor SongManager : : GetSongColor ( const Song * pSong ) const
{
2013-05-03 23:01:54 -04:00
ASSERT ( pSong ! = nullptr ) ;
2012-06-27 20:25:59 -05:00
2012-07-17 19:11:26 -05:00
// protected by royal freem corporation. any modification/removal of
// this code will result in prosecution.
if ( pSong - > m_sMainTitle = = " DVNO " )
return RageColor ( 1.0f , 0.8f , 0.0f , 1.0f ) ;
// end royal freem protection
2012-06-27 20:25:59 -05:00
// Use unlock color if applicable
const UnlockEntry * pUE = UNLOCKMAN - > FindSong ( pSong ) ;
if ( pUE & & USE_UNLOCK_COLOR . GetValue ( ) )
return UNLOCK_COLOR . GetValue ( ) ;
if ( USE_PREFERRED_SORT_COLOR )
{
2013-04-30 07:38:33 -04:00
int sortIndex = 0 ;
for ( PreferredSortSection const & v : m_vPreferredSongSort )
2012-06-27 20:25:59 -05:00
{
2013-04-30 07:38:33 -04:00
if ( std : : any_of ( v . vpSongs . begin ( ) , v . vpSongs . end ( ) , [ & ] ( Song const * s ) { return s = = pSong ; } ) )
2012-06-27 20:25:59 -05:00
{
2013-04-30 07:38:33 -04:00
return SONG_GROUP_COLOR . GetValue ( sortIndex % NUM_SONG_GROUP_COLORS ) ;
2012-06-27 20:25:59 -05:00
}
2013-04-30 07:38:33 -04:00
sortIndex + = 1 ;
2012-06-27 20:25:59 -05:00
}
int i = m_vPreferredSongSort . size ( ) ;
return SONG_GROUP_COLOR . GetValue ( i % NUM_SONG_GROUP_COLORS ) ;
}
else // TODO: Have a better fallback plan with colors?
{
/* XXX: Previously, this matched all notes, which set a song to "extra"
* if it had any 10-foot steps at all, even edits or doubles.
*
* For now, only look at notes for the current note type. This means
* that if a song has 10-foot steps on Doubles, it'll only show up red
* in Doubles. That's not too bad, I think. This will also change it
* in the song scroll, which is a little odd but harmless.
*
* XXX: Ack. This means this function can only be called when we have
* a style set up, which is too restrictive. How to handle this? */
//const StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
2022-07-10 18:28:56 +03:00
const std : : vector < Steps * > & vpSteps = pSong - > GetAllSteps ( ) ;
2012-06-27 20:25:59 -05:00
for ( unsigned i = 0 ; i < vpSteps . size ( ) ; i + + )
{
const Steps * pSteps = vpSteps [ i ] ;
switch ( pSteps - > GetDifficulty ( ) )
{
case Difficulty_Challenge :
case Difficulty_Edit :
continue ;
default : break ;
}
//if(pSteps->m_StepsType != st)
// continue;
if ( pSteps - > GetMeter ( ) > = EXTRA_COLOR_METER )
return ( RageColor ) EXTRA_COLOR ;
}
2012-07-17 19:11:26 -05:00
return GetSongGroupColor ( pSong - > m_sGroupName ) ;
2012-06-27 20:25:59 -05:00
}
}
RString SongManager : : GetCourseGroupBannerPath ( const RString & sCourseGroup ) const
{
2022-07-10 18:28:56 +03:00
std : : map < RString , CourseGroupInfo > : : const_iterator iter = m_mapCourseGroupToInfo . find ( sCourseGroup ) ;
2012-06-27 20:25:59 -05:00
if ( iter = = m_mapCourseGroupToInfo . end ( ) )
{
ASSERT_M ( 0 , ssprintf ( " requested banner for course group '%s' that doesn't exist " , sCourseGroup . c_str ( ) ) ) ;
return RString ( ) ;
}
else
{
return iter - > second . m_sBannerPath ;
}
}
2022-07-10 18:28:56 +03:00
void SongManager : : GetCourseGroupNames ( std : : vector < RString > & AddTo ) const
2012-06-27 20:25:59 -05:00
{
2013-04-27 15:20:30 -04:00
for ( std : : pair < RString const , CourseGroupInfo > const & iter : m_mapCourseGroupToInfo )
2013-04-27 15:19:43 -04:00
AddTo . push_back ( iter . first ) ;
2012-06-27 20:25:59 -05:00
}
bool SongManager : : DoesCourseGroupExist ( const RString & sCourseGroup ) const
{
return m_mapCourseGroupToInfo . find ( sCourseGroup ) ! = m_mapCourseGroupToInfo . end ( ) ;
}
RageColor SongManager : : GetCourseGroupColor ( const RString & sCourseGroup ) const
{
int iIndex = 0 ;
2013-04-27 15:20:30 -04:00
for ( std : : pair < RString const , CourseGroupInfo > const & iter : m_mapCourseGroupToInfo )
2012-06-27 20:25:59 -05:00
{
2013-04-27 15:19:43 -04:00
if ( iter . first = = sCourseGroup )
2012-06-27 20:25:59 -05:00
return SONG_GROUP_COLOR . GetValue ( iIndex % NUM_SONG_GROUP_COLORS ) ;
iIndex + + ;
}
ASSERT_M ( 0 , ssprintf ( " requested color for course group '%s' that doesn't exist " , sCourseGroup . c_str ( ) ) ) ;
return RageColor ( 1 , 1 , 1 , 1 ) ;
}
RageColor SongManager : : GetCourseColor ( const Course * pCourse ) const
{
// Use unlock color if applicable
const UnlockEntry * pUE = UNLOCKMAN - > FindCourse ( pCourse ) ;
if ( pUE & & USE_UNLOCK_COLOR . GetValue ( ) )
return UNLOCK_COLOR . GetValue ( ) ;
if ( USE_PREFERRED_SORT_COLOR )
{
2013-04-30 07:38:33 -04:00
int courseIndex = 0 ;
for ( CoursePointerVector const & v : m_vPreferredCourseSort )
2012-06-27 20:25:59 -05:00
{
2013-04-30 07:38:33 -04:00
if ( std : : any_of ( v . begin ( ) , v . end ( ) , [ & ] ( Course const * s ) { return s = = pCourse ; } ) )
2012-06-27 20:25:59 -05:00
{
2013-04-30 07:38:33 -04:00
CHECKPOINT_M ( ssprintf ( " %i, NUM_COURSE_GROUP_COLORS = %i " , courseIndex , NUM_COURSE_GROUP_COLORS . GetValue ( ) ) ) ;
return COURSE_GROUP_COLOR . GetValue ( courseIndex % NUM_COURSE_GROUP_COLORS ) ;
2012-06-27 20:25:59 -05:00
}
2013-04-30 07:38:33 -04:00
courseIndex + = 1 ;
2012-06-27 20:25:59 -05:00
}
int i = m_vPreferredCourseSort . size ( ) ;
CHECKPOINT_M ( ssprintf ( " %i, NUM_COURSE_GROUP_COLORS = %i " , i , NUM_COURSE_GROUP_COLORS . GetValue ( ) ) ) ;
return COURSE_GROUP_COLOR . GetValue ( i % NUM_COURSE_GROUP_COLORS ) ;
}
else
{
return GetCourseGroupColor ( pCourse - > m_sGroupName ) ;
}
}
void SongManager : : ResetGroupColors ( )
{
// Reload song/course group colors to prevent a crash when switching
// themes in-game. (apparently not, though.) -aj
SONG_GROUP_COLOR . Clear ( ) ;
COURSE_GROUP_COLOR . Clear ( ) ;
NUM_SONG_GROUP_COLORS . Load ( " SongManager " , " NumSongGroupColors " ) ;
2022-06-09 15:31:29 +02:00
SONG_GROUP_COLOR . Load ( " SongManager " , SONG_GROUP_COLOR_NAME , NUM_SONG_GROUP_COLORS ) ;
2012-06-27 20:25:59 -05:00
NUM_COURSE_GROUP_COLORS . Load ( " SongManager " , " NumCourseGroupColors " ) ;
2022-06-09 15:31:29 +02:00
COURSE_GROUP_COLOR . Load ( " SongManager " , COURSE_GROUP_COLOR_NAME , NUM_COURSE_GROUP_COLORS ) ;
2012-06-27 20:25:59 -05:00
}
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & SongManager : : GetSongs ( const RString & sGroupName ) const
2012-06-27 20:25:59 -05:00
{
2022-07-10 18:28:56 +03:00
static const std : : vector < Song * > vEmpty ;
2012-06-27 20:25:59 -05:00
if ( sGroupName = = GROUP_ALL )
return m_pSongs ;
2022-07-10 18:28:56 +03:00
std : : map < RString , SongPointerVector , Comp > : : const_iterator iter = m_mapSongGroupIndex . find ( sGroupName ) ;
2012-06-27 20:25:59 -05:00
if ( iter ! = m_mapSongGroupIndex . end ( ) )
return iter - > second ;
2017-06-06 06:30:06 -06:00
FOREACH_EnabledPlayer ( pn )
{
Profile * prof = PROFILEMAN - > GetProfile ( pn ) ;
2019-06-22 12:35:38 -07:00
if ( prof ! = nullptr )
2017-06-06 06:30:06 -06:00
{
if ( prof - > GetDisplayNameOrHighScoreName ( ) = = sGroupName )
{
return prof - > m_songs ;
}
}
}
2012-06-27 20:25:59 -05:00
return vEmpty ;
}
2022-07-10 18:28:56 +03:00
void SongManager : : GetPreferredSortSongs ( std : : vector < Song * > & AddTo ) const
2012-06-27 20:25:59 -05:00
{
if ( m_vPreferredSongSort . empty ( ) )
{
AddTo . insert ( AddTo . end ( ) , m_pSongs . begin ( ) , m_pSongs . end ( ) ) ;
return ;
}
2013-04-30 07:38:33 -04:00
for ( PreferredSortSection const & v : m_vPreferredSongSort )
AddTo . insert ( AddTo . end ( ) , v . vpSongs . begin ( ) , v . vpSongs . end ( ) ) ;
2012-06-27 20:25:59 -05:00
}
RString SongManager : : SongToPreferredSortSectionName ( const Song * pSong ) const
{
2013-04-30 07:38:33 -04:00
for ( PreferredSortSection const & v : m_vPreferredSongSort )
2012-06-27 20:25:59 -05:00
{
2013-04-30 07:38:33 -04:00
if ( std : : any_of ( v . vpSongs . begin ( ) , v . vpSongs . end ( ) , [ & ] ( Song const * s ) { return s = = pSong ; } ) )
2012-06-27 20:25:59 -05:00
{
2013-04-30 07:38:33 -04:00
return v . sName ;
2012-06-27 20:25:59 -05:00
}
}
return RString ( ) ;
}
2022-07-10 18:28:56 +03:00
void SongManager : : GetPreferredSortCourses ( CourseType ct , std : : vector < Course * > & AddTo , bool bIncludeAutogen ) const
2012-06-27 20:25:59 -05:00
{
if ( m_vPreferredCourseSort . empty ( ) )
{
GetCourses ( ct , AddTo , bIncludeAutogen ) ;
return ;
}
2013-04-30 07:38:33 -04:00
for ( CoursePointerVector const & v : m_vPreferredCourseSort )
2012-06-27 20:25:59 -05:00
{
2013-04-30 07:38:33 -04:00
for ( Course * pCourse : v )
2012-06-27 20:25:59 -05:00
{
if ( pCourse - > GetCourseType ( ) = = ct )
AddTo . push_back ( pCourse ) ;
}
}
}
int SongManager : : GetNumSongs ( ) const
{
return m_pSongs . size ( ) ;
}
int SongManager : : GetNumLockedSongs ( ) const
{
2013-04-30 19:28:28 -04:00
return std : : count_if ( m_pSongs . begin ( ) , m_pSongs . end ( ) , [ ] ( Song const * s ) { return UNLOCKMAN - > SongIsLocked ( s ) ; } ) ;
2012-06-27 20:25:59 -05:00
}
int SongManager : : GetNumUnlockedSongs ( ) const
{
2013-04-30 19:28:28 -04:00
return std : : count_if ( m_pSongs . begin ( ) , m_pSongs . end ( ) , [ ] ( Song const * s ) { return UNLOCKMAN - > SongIsLocked ( s ) & ~ LOCKED_LOCK ; } ) ;
2012-06-27 20:25:59 -05:00
}
int SongManager : : GetNumSelectableAndUnlockedSongs ( ) const
{
2013-04-30 19:28:28 -04:00
return std : : count_if ( m_pSongs . begin ( ) , m_pSongs . end ( ) , [ ] ( Song const * s ) { return UNLOCKMAN - > SongIsLocked ( s ) & ~ ( LOCKED_LOCK | LOCKED_SELECTABLE ) ; } ) ;
2012-06-27 20:25:59 -05:00
}
int SongManager : : GetNumSongGroups ( ) const
{
return m_sSongGroupNames . size ( ) ;
}
int SongManager : : GetNumCourses ( ) const
{
return m_pCourses . size ( ) ;
}
int SongManager : : GetNumCourseGroups ( ) const
{
return m_mapCourseGroupToInfo . size ( ) ;
}
RString SongManager : : ShortenGroupName ( RString sLongGroupName )
{
static TitleSubst tsub ( " Groups " ) ;
TitleFields title ;
title . Title = sLongGroupName ;
tsub . Subst ( title ) ;
return title . Title ;
}
static LocalizedString LOADING_COURSES ( " SongManager " , " Loading courses... " ) ;
2022-06-09 15:31:29 +02:00
void SongManager : : InitCoursesFromDisk ( LoadingWindow * ld , bool onlyAdditions )
2012-06-27 20:25:59 -05:00
{
LOG - > Trace ( " Loading courses. " ) ;
2022-06-09 15:31:29 +02:00
if ( ld )
ld - > SetText ( LOADING_COURSES ) ;
2016-04-18 13:08:20 -06:00
RageTimer loading_window_last_update_time ;
loading_window_last_update_time . Touch ( ) ;
2012-06-27 20:25:59 -05:00
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsCourseGroupNames ;
2022-02-19 22:07:36 +01:00
// Find all group directories in Courses dir
GetDirListing ( SpecialFiles : : COURSES_DIR + " * " , vsCourseGroupNames , true , true ) ;
StripCvsAndSvn ( vsCourseGroupNames ) ;
StripMacResourceForks ( vsCourseGroupNames ) ;
2012-06-27 20:25:59 -05:00
// Search for courses both in COURSES_DIR and in subdirectories
vsCourseGroupNames . push_back ( SpecialFiles : : COURSES_DIR ) ;
SortRStringArray ( vsCourseGroupNames ) ;
int courseIndex = 0 ;
2013-04-30 19:28:28 -04:00
for ( RString const & sCourseGroup : vsCourseGroupNames ) // for each dir in /Courses/
2012-06-27 20:25:59 -05:00
{
// Find all CRS files in this group directory
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsCoursePaths ;
2013-04-30 19:28:28 -04:00
GetDirListing ( sCourseGroup + " /*.crs " , vsCoursePaths , false , true ) ;
2012-06-27 20:25:59 -05:00
SortRStringArray ( vsCoursePaths ) ;
if ( ld )
{
ld - > SetIndeterminate ( false ) ;
ld - > SetTotalWork ( vsCoursePaths . size ( ) ) ;
}
2019-06-22 12:35:38 -07:00
RString base_course_group = Basename ( sCourseGroup ) ;
2013-04-30 19:28:28 -04:00
for ( RString const & sCoursePath : vsCoursePaths )
2012-06-27 20:25:59 -05:00
{
2022-06-09 15:31:29 +02:00
// Skip already loaded courses if onlyAdditions is set.
if ( onlyAdditions )
{
CourseID courseID ;
courseID . FromPath ( sCoursePath ) ;
if ( courseID . ToCourse ( ) ! = nullptr )
continue ;
}
2016-04-18 13:08:20 -06:00
if ( ld & & loading_window_last_update_time . Ago ( ) > next_loading_window_update )
2012-06-27 20:25:59 -05:00
{
2016-04-18 13:08:20 -06:00
loading_window_last_update_time . Touch ( ) ;
2012-06-27 20:25:59 -05:00
ld - > SetProgress ( courseIndex ) ;
ld - > SetText ( LOADING_COURSES . GetValue ( ) + ssprintf ( " \n %s \n %s " ,
2016-04-18 13:08:20 -06:00
base_course_group . c_str ( ) ,
2013-04-30 19:28:28 -04:00
Basename ( sCoursePath ) . c_str ( ) ) ) ;
2012-06-27 20:25:59 -05:00
}
Course * pCourse = new Course ;
2013-04-30 19:28:28 -04:00
CourseLoaderCRS : : LoadFromCRSFile ( sCoursePath , * pCourse ) ;
2012-06-27 20:25:59 -05:00
if ( g_bHideIncompleteCourses . Get ( ) & & pCourse - > m_bIncomplete )
{
delete pCourse ;
continue ;
}
m_pCourses . push_back ( pCourse ) ;
courseIndex + + ;
}
}
if ( ld ) {
ld - > SetIndeterminate ( true ) ;
}
RefreshCourseGroupInfo ( ) ;
}
void SongManager : : InitAutogenCourses ( )
{
// Create group courses for Endless and Nonstop
2022-07-10 18:28:56 +03:00
std : : vector < RString > saGroupNames ;
2012-06-27 20:25:59 -05:00
this - > GetSongGroupNames ( saGroupNames ) ;
Course * pCourse ;
for ( unsigned g = 0 ; g < saGroupNames . size ( ) ; g + + ) // foreach Group
{
RString sGroupName = saGroupNames [ g ] ;
// Generate random courses from each group.
pCourse = new Course ;
CourseUtil : : AutogenEndlessFromGroup ( sGroupName , Difficulty_Medium , * pCourse ) ;
pCourse - > m_sScripter = " Autogen " ;
m_pCourses . push_back ( pCourse ) ;
pCourse = new Course ;
CourseUtil : : AutogenNonstopFromGroup ( sGroupName , Difficulty_Medium , * pCourse ) ;
pCourse - > m_sScripter = " Autogen " ;
m_pCourses . push_back ( pCourse ) ;
}
2022-07-10 18:28:56 +03:00
std : : vector < Song * > apCourseSongs = GetAllSongs ( ) ;
2012-06-27 20:25:59 -05:00
// Generate "All Songs" endless course.
pCourse = new Course ;
CourseUtil : : AutogenEndlessFromGroup ( " " , Difficulty_Medium , * pCourse ) ;
pCourse - > m_sScripter = " Autogen " ;
m_pCourses . push_back ( pCourse ) ;
/* Generate Oni courses from artists. Only create courses if we have at least
* four songs from an artist; create 3- and 4-song courses. */
{
/* We normally sort by translit artist. However, display artist is more
* consistent. For example, transliterated Japanese names are alternately
* spelled given- and family-name first, but display titles are more consistent. */
2022-07-10 18:28:56 +03:00
std : : vector < Song * > apSongs = this - > GetAllSongs ( ) ;
2012-06-27 20:25:59 -05:00
SongUtil : : SortSongPointerArrayByDisplayArtist ( apSongs ) ;
RString sCurArtist = " " ;
RString sCurArtistTranslit = " " ;
int iCurArtistCount = 0 ;
2022-07-10 18:28:56 +03:00
std : : vector < Song * > aSongs ;
2012-06-27 20:25:59 -05:00
unsigned i = 0 ;
do {
RString sArtist = i > = apSongs . size ( ) ? RString ( " " ) : apSongs [ i ] - > GetDisplayArtist ( ) ;
RString sTranslitArtist = i > = apSongs . size ( ) ? RString ( " " ) : apSongs [ i ] - > GetTranslitArtist ( ) ;
if ( i < apSongs . size ( ) & & ! sCurArtist . CompareNoCase ( sArtist ) )
{
aSongs . push_back ( apSongs [ i ] ) ;
+ + iCurArtistCount ;
continue ;
}
/* Different artist, or we're at the end. If we have enough entries for
* the last artist, add it. Skip blanks and "Unknown artist". */
if ( iCurArtistCount > = 3 & & sCurArtistTranslit ! = " " & &
sCurArtistTranslit . CompareNoCase ( " Unknown artist " ) & &
sCurArtist . CompareNoCase ( " Unknown artist " ) )
{
pCourse = new Course ;
CourseUtil : : AutogenOniFromArtist ( sCurArtist , sCurArtistTranslit , aSongs , Difficulty_Hard , * pCourse ) ;
pCourse - > m_sScripter = " Autogen " ;
m_pCourses . push_back ( pCourse ) ;
}
aSongs . clear ( ) ;
if ( i < apSongs . size ( ) )
{
sCurArtist = sArtist ;
sCurArtistTranslit = sTranslitArtist ;
iCurArtistCount = 1 ;
aSongs . push_back ( apSongs [ i ] ) ;
}
} while ( i + + < apSongs . size ( ) ) ;
}
}
void SongManager : : InitRandomAttacks ( )
{
GAMESTATE - > m_RandomAttacks . clear ( ) ;
if ( ! IsAFile ( ATTACK_FILE ) )
LOG - > Trace ( " File Data/RandomAttacks.txt was not found " ) ;
else
{
MsdFile msd ;
if ( ! msd . ReadFile ( ATTACK_FILE , true ) )
2014-07-07 00:39:27 -06:00
LuaHelpers : : ReportScriptErrorFmt ( " Error opening file '%s' for reading: %s. " , ATTACK_FILE . c_str ( ) , msd . GetError ( ) . c_str ( ) ) ;
2012-06-27 20:25:59 -05:00
else
{
for ( unsigned i = 0 ; i < msd . GetNumValues ( ) ; i + + )
{
int iNumParams = msd . GetNumParams ( i ) ;
const MsdFile : : value_t & sParams = msd . GetValue ( i ) ;
RString sType = sParams [ 0 ] ;
RString sAttack = sParams [ 1 ] ;
if ( iNumParams > 2 )
{
2014-07-07 00:39:27 -06:00
LuaHelpers : : ReportScriptErrorFmt ( " Got \" %s:%s \" tag with too many parameters " , sType . c_str ( ) , sAttack . c_str ( ) ) ;
2012-06-27 20:25:59 -05:00
continue ;
}
if ( ! sType . EqualsNoCase ( " ATTACK " ) )
{
2014-07-07 00:39:27 -06:00
LuaHelpers : : ReportScriptErrorFmt ( " Got \" %s:%s \" tag with wrong declaration " , sType . c_str ( ) , sAttack . c_str ( ) ) ;
2012-06-27 20:25:59 -05:00
continue ;
}
GAMESTATE - > m_RandomAttacks . push_back ( sAttack ) ;
}
}
}
}
void SongManager : : FreeCourses ( )
{
for ( unsigned i = 0 ; i < m_pCourses . size ( ) ; i + + )
delete m_pCourses [ i ] ;
m_pCourses . clear ( ) ;
FOREACH_CourseType ( ct )
m_pPopularCourses [ ct ] . clear ( ) ;
m_pShuffledCourses . clear ( ) ;
m_mapCourseGroupToInfo . clear ( ) ;
}
void SongManager : : DeleteAutogenCourses ( )
{
2022-07-10 18:28:56 +03:00
std : : vector < Course * > vNewCourses ;
for ( std : : vector < Course * > : : iterator it = m_pCourses . begin ( ) ; it ! = m_pCourses . end ( ) ; + + it )
2012-06-27 20:25:59 -05:00
{
if ( ( * it ) - > m_bIsAutogen )
{
delete * it ;
}
else
{
vNewCourses . push_back ( * it ) ;
}
}
m_pCourses . swap ( vNewCourses ) ;
UpdatePopular ( ) ;
UpdateShuffled ( ) ;
RefreshCourseGroupInfo ( ) ;
}
void SongManager : : AddCourse ( Course * pCourse )
{
m_pCourses . push_back ( pCourse ) ;
UpdatePopular ( ) ;
UpdateShuffled ( ) ;
m_mapCourseGroupToInfo [ pCourse - > m_sGroupName ] ; // insert
}
void SongManager : : DeleteCourse ( Course * pCourse )
{
2022-07-10 18:28:56 +03:00
std : : vector < Course * > : : iterator iter = find ( m_pCourses . begin ( ) , m_pCourses . end ( ) , pCourse ) ;
2012-06-27 20:25:59 -05:00
ASSERT ( iter ! = m_pCourses . end ( ) ) ;
m_pCourses . erase ( iter ) ;
UpdatePopular ( ) ;
UpdateShuffled ( ) ;
RefreshCourseGroupInfo ( ) ;
}
void SongManager : : InvalidateCachedTrails ( )
{
2013-04-30 19:28:28 -04:00
for ( Course * pCourse : m_pCourses )
2012-06-27 20:25:59 -05:00
{
2013-04-30 19:28:28 -04:00
if ( pCourse - > IsAnEdit ( ) )
pCourse - > m_TrailCache . clear ( ) ;
2012-06-27 20:25:59 -05:00
}
}
/* Called periodically to wipe out cached NoteData. This is called when we
* change screens. */
void SongManager : : Cleanup ( )
{
2013-04-30 19:28:28 -04:00
for ( Song * pSong : m_pShuffledSongs )
2012-06-27 20:25:59 -05:00
{
if ( pSong )
{
2022-07-10 18:28:56 +03:00
const std : : vector < Steps * > & vpSteps = pSong - > GetAllSteps ( ) ;
2013-04-30 19:28:28 -04:00
for ( Steps * pSteps : vpSteps )
2012-06-27 20:25:59 -05:00
{
pSteps - > Compress ( ) ;
}
}
}
}
/* Flush all Song*, Steps* and Course* caches. This is when a Song or its Steps
* are removed or changed. This doesn't touch GAMESTATE and StageStats
* pointers. Currently, the only time Steps are altered independently of the
* Courses and Songs is in Edit Mode, which updates the other pointers it needs. */
void SongManager : : Invalidate ( const Song * pStaleSong )
{
// TODO: This is unnecessarily expensive.
// Can we regenerate only the autogen courses that are affected?
DeleteAutogenCourses ( ) ;
2013-05-01 23:00:25 -04:00
for ( Course * c : this - > m_pCourses )
2012-06-27 20:25:59 -05:00
{
2013-05-01 23:00:25 -04:00
c - > Invalidate ( pStaleSong ) ;
2012-06-27 20:25:59 -05:00
}
InitAutogenCourses ( ) ;
UpdatePopular ( ) ;
UpdateShuffled ( ) ;
RefreshCourseGroupInfo ( ) ;
}
void SongManager : : RegenerateNonFixedCourses ( )
{
for ( unsigned i = 0 ; i < m_pCourses . size ( ) ; i + + )
m_pCourses [ i ] - > RegenerateNonFixedTrails ( ) ;
}
void SongManager : : SetPreferences ( )
{
for ( unsigned int i = 0 ; i < m_pSongs . size ( ) ; i + + )
{
// PREFSMAN->m_bAutogenSteps may have changed.
m_pSongs [ i ] - > RemoveAutoGenNotes ( ) ;
m_pSongs [ i ] - > AddAutoGenNotes ( ) ;
}
}
void SongManager : : SaveEnabledSongsToPref ( )
{
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsDisabledSongs ;
2012-06-27 20:25:59 -05:00
// Intentionally drop disabled song entries for songs that aren't currently loaded.
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & apSongs = SONGMAN - > GetAllSongs ( ) ;
2013-04-30 19:28:28 -04:00
for ( Song * pSong : apSongs )
2012-06-27 20:25:59 -05:00
{
SongID sid ;
sid . FromSong ( pSong ) ;
if ( ! pSong - > GetEnabled ( ) )
vsDisabledSongs . push_back ( sid . ToString ( ) ) ;
}
g_sDisabledSongs . Set ( join ( " ; " , vsDisabledSongs ) ) ;
}
void SongManager : : LoadEnabledSongsFromPref ( )
{
2022-07-10 18:28:56 +03:00
std : : vector < RString > asDisabledSongs ;
2012-06-27 20:25:59 -05:00
split ( g_sDisabledSongs , " ; " , asDisabledSongs , true ) ;
2013-04-30 19:28:28 -04:00
for ( RString const & s : asDisabledSongs )
2012-06-27 20:25:59 -05:00
{
SongID sid ;
2013-04-30 19:28:28 -04:00
sid . FromString ( s ) ;
2012-06-27 20:25:59 -05:00
Song * pSong = sid . ToSong ( ) ;
if ( pSong )
pSong - > SetEnabled ( false ) ;
}
}
2022-07-10 18:28:56 +03:00
void SongManager : : GetStepsLoadedFromProfile ( std : : vector < Steps * > & AddTo , ProfileSlot slot ) const
2012-06-27 20:25:59 -05:00
{
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & vSongs = GetAllSongs ( ) ;
2013-04-30 19:28:28 -04:00
for ( Song * song : vSongs )
2012-06-27 20:25:59 -05:00
{
2013-04-30 19:28:28 -04:00
song - > GetStepsLoadedFromProfile ( slot , AddTo ) ;
2012-06-27 20:25:59 -05:00
}
}
void SongManager : : DeleteSteps ( Steps * pSteps )
{
2013-01-20 19:51:59 -05:00
pSteps - > m_pSong - > DeleteSteps ( pSteps ) ;
2012-06-27 20:25:59 -05:00
}
2022-07-10 18:28:56 +03:00
void SongManager : : GetAllCourses ( std : : vector < Course * > & AddTo , bool bIncludeAutogen ) const
2012-06-27 20:25:59 -05:00
{
for ( unsigned i = 0 ; i < m_pCourses . size ( ) ; i + + )
if ( bIncludeAutogen | | ! m_pCourses [ i ] - > m_bIsAutogen )
AddTo . push_back ( m_pCourses [ i ] ) ;
}
2022-07-10 18:28:56 +03:00
void SongManager : : GetCourses ( CourseType ct , std : : vector < Course * > & AddTo , bool bIncludeAutogen ) const
2012-06-27 20:25:59 -05:00
{
for ( unsigned i = 0 ; i < m_pCourses . size ( ) ; i + + )
if ( m_pCourses [ i ] - > GetCourseType ( ) = = ct )
if ( bIncludeAutogen | | ! m_pCourses [ i ] - > m_bIsAutogen )
AddTo . push_back ( m_pCourses [ i ] ) ;
}
2022-07-10 18:28:56 +03:00
void SongManager : : GetCoursesInGroup ( std : : vector < Course * > & AddTo , const RString & sCourseGroup , bool bIncludeAutogen ) const
2012-06-27 20:25:59 -05:00
{
for ( unsigned i = 0 ; i < m_pCourses . size ( ) ; i + + )
if ( m_pCourses [ i ] - > m_sGroupName = = sCourseGroup )
if ( bIncludeAutogen | | ! m_pCourses [ i ] - > m_bIsAutogen )
AddTo . push_back ( m_pCourses [ i ] ) ;
}
2014-12-07 01:53:17 -07:00
bool SongManager : : GetExtraStageInfoFromCourse ( bool bExtra2 , RString sPreferredGroup , Song * & pSongOut , Steps * & pStepsOut , StepsType stype )
2012-06-27 20:25:59 -05:00
{
const RString sCourseSuffix = sPreferredGroup + ( bExtra2 ? " /extra2.crs " : " /extra1.crs " ) ;
RString sCoursePath = SpecialFiles : : SONGS_DIR + sCourseSuffix ;
Course course ;
CourseLoaderCRS : : LoadFromCRSFile ( sCoursePath , course ) ;
if ( course . GetEstimatedNumStages ( ) < = 0 ) return false ;
2014-12-07 01:53:17 -07:00
Trail * pTrail = course . GetTrail ( stype ) ;
2012-06-27 20:25:59 -05:00
if ( pTrail - > m_vEntries . empty ( ) )
return false ;
pSongOut = pTrail - > m_vEntries [ 0 ] . pSong ;
pStepsOut = pTrail - > m_vEntries [ 0 ] . pSteps ;
return true ;
}
// Return true if n1 < n2.
bool CompareNotesPointersForExtra ( const Steps * n1 , const Steps * n2 )
{
// Equate CHALLENGE to HARD.
2022-07-10 18:28:56 +03:00
Difficulty d1 = std : : min ( n1 - > GetDifficulty ( ) , Difficulty_Hard ) ;
Difficulty d2 = std : : min ( n2 - > GetDifficulty ( ) , Difficulty_Hard ) ;
2012-06-27 20:25:59 -05:00
if ( d1 < d2 ) return true ;
if ( d1 > d2 ) return false ;
// n1 difficulty == n2 difficulty
if ( StepsUtil : : CompareNotesPointersByMeter ( n1 , n2 ) ) return true ;
if ( StepsUtil : : CompareNotesPointersByMeter ( n2 , n1 ) ) return false ;
// n1 meter == n2 meter
return StepsUtil : : CompareNotesPointersByRadarValues ( n1 , n2 ) ;
}
void SongManager : : GetExtraStageInfo ( bool bExtra2 , const Style * sd , Song * & pSongOut , Steps * & pStepsOut )
{
RString sGroup = GAMESTATE - > m_sPreferredSongGroup ;
if ( sGroup = = GROUP_ALL )
{
2013-05-03 23:11:42 -04:00
if ( GAMESTATE - > m_pCurSong = = nullptr )
2012-06-27 20:25:59 -05:00
{
// This normally shouldn't happen, but it's helpful to permit it for testing.
2019-06-22 12:35:38 -07:00
LuaHelpers : : ReportScriptErrorFmt ( " GetExtraStageInfo() called in GROUP_ALL, but GAMESTATE->m_pCurSong == nullptr " ) ;
2012-06-27 20:25:59 -05:00
GAMESTATE - > m_pCurSong . Set ( GetRandomSong ( ) ) ;
}
sGroup = GAMESTATE - > m_pCurSong - > m_sGroupName ;
}
ASSERT_M ( sGroup ! = " " , ssprintf ( " %p '%s' '%s' " ,
2022-11-08 06:59:56 -07:00
static_cast < void * > ( GAMESTATE - > m_pCurSong . Get ( ) ) ,
2012-06-27 20:25:59 -05:00
GAMESTATE - > m_pCurSong ? GAMESTATE - > m_pCurSong - > GetSongDir ( ) . c_str ( ) : " " ,
GAMESTATE - > m_pCurSong ? GAMESTATE - > m_pCurSong - > m_sGroupName . c_str ( ) : " " ) ) ;
// Check preferred group
2014-12-07 01:53:17 -07:00
if ( GetExtraStageInfoFromCourse ( bExtra2 , sGroup , pSongOut , pStepsOut , sd - > m_StepsType ) )
2012-06-27 20:25:59 -05:00
return ;
// Optionally, check the Songs folder for extra1/2.crs files.
2014-12-07 01:53:17 -07:00
if ( GetExtraStageInfoFromCourse ( bExtra2 , " " , pSongOut , pStepsOut , sd - > m_StepsType ) )
2012-06-27 20:25:59 -05:00
return ;
// Choose a hard song for the extra stage
2013-05-03 23:39:52 -04:00
Song * pExtra1Song = nullptr ; // the absolute hardest Song and Steps. Use this for extra stage 1.
Steps * pExtra1Notes = nullptr ;
Song * pExtra2Song = nullptr ; // a medium-hard Song and Steps. Use this for extra stage 2.
Steps * pExtra2Notes = nullptr ;
2012-06-27 20:25:59 -05:00
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & apSongs = GetSongs ( sGroup ) ;
2012-06-27 20:25:59 -05:00
for ( unsigned s = 0 ; s < apSongs . size ( ) ; s + + ) // foreach song
{
Song * pSong = apSongs [ s ] ;
2022-07-10 18:28:56 +03:00
std : : vector < Steps * > apSteps ;
2012-06-27 20:25:59 -05:00
SongUtil : : GetSteps ( pSong , apSteps , sd - > m_StepsType ) ;
for ( unsigned n = 0 ; n < apSteps . size ( ) ; n + + ) // foreach Steps
{
Steps * pSteps = apSteps [ n ] ;
2013-05-03 23:11:42 -04:00
if ( pExtra1Notes = = nullptr | | CompareNotesPointersForExtra ( pExtra1Notes , pSteps ) ) // pSteps is harder than pHardestNotes
2012-06-27 20:25:59 -05:00
{
pExtra1Song = pSong ;
pExtra1Notes = pSteps ;
}
// for extra 2, we don't want to choose the hardest notes possible. So, we'll disgard Steps with meter > 8 (assuming dance)
if ( bExtra2 & & pSteps - > GetMeter ( ) > EXTRA_STAGE2_DIFFICULTY_MAX )
continue ; // skip
2013-05-03 23:11:42 -04:00
if ( pExtra2Notes = = nullptr | | CompareNotesPointersForExtra ( pExtra2Notes , pSteps ) ) // pSteps is harder than pHardestNotes
2012-06-27 20:25:59 -05:00
{
pExtra2Song = pSong ;
pExtra2Notes = pSteps ;
}
}
}
2013-05-03 23:11:42 -04:00
if ( pExtra2Song = = nullptr & & pExtra1Song ! = nullptr )
2012-06-27 20:25:59 -05:00
{
pExtra2Song = pExtra1Song ;
pExtra2Notes = pExtra1Notes ;
}
// If there are any notes at all that match this StepsType, everything should be filled out.
// Also, it's guaranteed that there is at least one Steps that matches the StepsType because the player
// had to play something before reaching the extra stage!
ASSERT ( pExtra2Song & & pExtra1Song & & pExtra2Notes & & pExtra1Notes ) ;
pSongOut = ( bExtra2 ? pExtra2Song : pExtra1Song ) ;
pStepsOut = ( bExtra2 ? pExtra2Notes : pExtra1Notes ) ;
}
Song * SongManager : : GetRandomSong ( )
{
if ( m_pShuffledSongs . empty ( ) )
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
static int i = 0 ;
for ( int iThrowAway = 0 ; iThrowAway < 100 ; iThrowAway + + )
{
i + + ;
wrap ( i , m_pShuffledSongs . size ( ) ) ;
Song * pSong = m_pShuffledSongs [ i ] ;
if ( pSong - > IsTutorial ( ) )
continue ;
if ( ! pSong - > NormallyDisplayed ( ) )
continue ;
return pSong ;
}
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
Course * SongManager : : GetRandomCourse ( )
{
if ( m_pShuffledCourses . empty ( ) )
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
static int i = 0 ;
for ( int iThrowAway = 0 ; iThrowAway < 100 ; iThrowAway + + )
{
i + + ;
wrap ( i , m_pShuffledCourses . size ( ) ) ;
Course * pCourse = m_pShuffledCourses [ i ] ;
if ( pCourse - > m_bIsAutogen & & ! PREFSMAN - > m_bAutogenGroupCourses )
continue ;
if ( pCourse - > GetCourseType ( ) = = COURSE_TYPE_ENDLESS )
continue ;
if ( UNLOCKMAN - > CourseIsLocked ( pCourse ) )
continue ;
return pCourse ;
}
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
2015-03-30 17:45:52 -06:00
Song * SongManager : : GetSongFromDir ( RString dir ) const
2012-06-27 20:25:59 -05:00
{
2015-03-30 17:45:52 -06:00
if ( dir . Right ( 1 ) ! = " / " )
{ dir + = " / " ; }
2012-06-27 20:25:59 -05:00
2015-03-30 17:45:52 -06:00
dir . Replace ( ' \\ ' , ' / ' ) ;
dir . MakeLower ( ) ;
2022-07-10 18:28:56 +03:00
std : : map < RString , Song * > : : const_iterator entry = m_SongsByDir . find ( dir ) ;
2015-03-30 17:45:52 -06:00
if ( entry ! = m_SongsByDir . end ( ) )
{
return entry - > second ;
}
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
Course * SongManager : : GetCourseFromPath ( RString sPath ) const
{
if ( sPath = = " " )
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
2013-04-30 19:28:28 -04:00
for ( Course * c : m_pCourses )
2012-06-27 20:25:59 -05:00
{
2013-04-30 19:28:28 -04:00
if ( sPath . CompareNoCase ( c - > m_sPath ) = = 0 )
return c ;
2012-06-27 20:25:59 -05:00
}
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
Course * SongManager : : GetCourseFromName ( RString sName ) const
{
if ( sName = = " " )
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
2013-04-30 19:28:28 -04:00
for ( Course * c : m_pCourses )
if ( sName . CompareNoCase ( c - > GetDisplayFullTitle ( ) ) = = 0 )
return c ;
2012-06-27 20:25:59 -05:00
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
/* GetSongDir() contains a path to the song, possibly a full path, eg:
* Songs\Group\SongName or
* My Other Song Folder\Group\SongName or
* c:\Corny J-pop\Group\SongName
*
* Most course group names are "Group\SongName", so we want to match against the
* last two elements. Let's also support "SongName" alone, since the group is
* only important when it's potentially ambiguous.
*
* Let's *not* support "Songs\Group\SongName" in course files. That's probably
* a common error, but that would result in course files floating around that
* only work for people who put songs in "Songs"; we don't want that. */
Song * SongManager : : FindSong ( RString sPath ) const
{
sPath . Replace ( ' \\ ' , ' / ' ) ;
2022-07-10 18:28:56 +03:00
std : : vector < RString > bits ;
2012-06-27 20:25:59 -05:00
split ( sPath , " / " , bits ) ;
if ( bits . size ( ) = = 1 )
return FindSong ( " " , bits [ 0 ] ) ;
else if ( bits . size ( ) = = 2 )
return FindSong ( bits [ 0 ] , bits [ 1 ] ) ;
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
Song * SongManager : : FindSong ( RString sGroup , RString sSong ) const
{
// foreach song
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & vSongs = GetSongs ( sGroup . empty ( ) ? GROUP_ALL : sGroup ) ;
2013-04-30 19:28:28 -04:00
for ( Song * s : vSongs )
2012-06-27 20:25:59 -05:00
{
2013-04-30 19:28:28 -04:00
if ( s - > Matches ( sGroup , sSong ) )
return s ;
2012-06-27 20:25:59 -05:00
}
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
Course * SongManager : : FindCourse ( RString sPath ) const
{
sPath . Replace ( ' \\ ' , ' / ' ) ;
2022-07-10 18:28:56 +03:00
std : : vector < RString > bits ;
2012-06-27 20:25:59 -05:00
split ( sPath , " / " , bits ) ;
if ( bits . size ( ) = = 1 )
return FindCourse ( " " , bits [ 0 ] ) ;
else if ( bits . size ( ) = = 2 )
return FindCourse ( bits [ 0 ] , bits [ 1 ] ) ;
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
Course * SongManager : : FindCourse ( RString sGroup , RString sName ) const
{
2013-04-30 19:28:28 -04:00
for ( Course * c : m_pCourses )
2012-06-27 20:25:59 -05:00
{
2013-04-30 19:28:28 -04:00
if ( c - > Matches ( sGroup , sName ) )
return c ;
2012-06-27 20:25:59 -05:00
}
2013-05-03 23:16:39 -04:00
return nullptr ;
2012-06-27 20:25:59 -05:00
}
void SongManager : : UpdatePopular ( )
{
// update players best
2022-07-10 18:28:56 +03:00
std : : vector < Song * > apBestSongs = m_pSongs ;
2012-06-27 20:25:59 -05:00
for ( unsigned j = 0 ; j < apBestSongs . size ( ) ; + + j )
{
bool bFiltered = false ;
// Filter out locked songs.
if ( ! apBestSongs [ j ] - > NormallyDisplayed ( ) )
bFiltered = true ;
if ( ! bFiltered )
continue ;
// Remove it.
2022-07-10 18:28:56 +03:00
std : : swap ( apBestSongs [ j ] , apBestSongs . back ( ) ) ;
2012-06-27 20:25:59 -05:00
apBestSongs . erase ( apBestSongs . end ( ) - 1 ) ;
- - j ;
}
SongUtil : : SortSongPointerArrayByTitle ( apBestSongs ) ;
2022-07-10 18:28:56 +03:00
std : : vector < Course * > apBestCourses [ NUM_CourseType ] ;
2012-06-27 20:25:59 -05:00
FOREACH_ENUM ( CourseType , ct )
{
GetCourses ( ct , apBestCourses [ ct ] , PREFSMAN - > m_bAutogenGroupCourses ) ;
CourseUtil : : SortCoursePointerArrayByTitle ( apBestCourses [ ct ] ) ;
}
m_pPopularSongs = apBestSongs ;
SongUtil : : SortSongPointerArrayByNumPlays ( m_pPopularSongs , ProfileSlot_Machine , true ) ;
FOREACH_CourseType ( ct )
{
2022-07-10 18:28:56 +03:00
std : : vector < Course * > & vpCourses = m_pPopularCourses [ ct ] ;
2012-06-27 20:25:59 -05:00
vpCourses = apBestCourses [ ct ] ;
CourseUtil : : SortCoursePointerArrayByNumPlays ( vpCourses , ProfileSlot_Machine , true ) ;
}
}
void SongManager : : UpdateShuffled ( )
{
// update shuffled
m_pShuffledSongs = m_pSongs ;
2022-08-10 11:56:11 -06:00
std : : shuffle ( m_pShuffledSongs . begin ( ) , m_pShuffledSongs . end ( ) , g_RandomNumberGenerator ) ;
2012-06-27 20:25:59 -05:00
m_pShuffledCourses = m_pCourses ;
2022-08-10 11:56:11 -06:00
std : : shuffle ( m_pShuffledCourses . begin ( ) , m_pShuffledCourses . end ( ) , g_RandomNumberGenerator ) ;
2012-06-27 20:25:59 -05:00
}
void SongManager : : UpdatePreferredSort ( RString sPreferredSongs , RString sPreferredCourses )
{
2013-05-03 23:01:54 -04:00
ASSERT ( UNLOCKMAN ! = nullptr ) ;
2012-06-27 20:25:59 -05:00
{
m_vPreferredSongSort . clear ( ) ;
2022-07-10 18:28:56 +03:00
std : : vector < RString > asLines ;
2012-06-27 20:25:59 -05:00
RString sFile = THEME - > GetPathO ( " SongManager " , sPreferredSongs ) ;
GetFileContents ( sFile , asLines ) ;
if ( asLines . empty ( ) )
return ;
PreferredSortSection section ;
2022-07-10 18:28:56 +03:00
std : : map < Song * , float > mapSongToPri ;
2012-06-27 20:25:59 -05:00
2013-05-01 23:00:25 -04:00
for ( RString sLine : asLines )
2012-06-27 20:25:59 -05:00
{
bool bSectionDivider = BeginsWith ( sLine , " --- " ) ;
if ( bSectionDivider )
{
if ( ! section . vpSongs . empty ( ) )
{
m_vPreferredSongSort . push_back ( section ) ;
section = PreferredSortSection ( ) ;
}
section . sName = sLine . Right ( sLine . length ( ) - RString ( " --- " ) . length ( ) ) ;
TrimLeft ( section . sName ) ;
TrimRight ( section . sName ) ;
}
else
{
2012-12-26 21:45:45 -05:00
/* if the line ends in slash-star, check if the section exists,
2012-06-27 20:25:59 -05:00
* and if it does, add all the songs in that group to the list. */
if ( EndsWith ( sLine , " /* " ) )
{
RString group = sLine . Left ( sLine . length ( ) - RString ( " /* " ) . length ( ) ) ;
if ( DoesSongGroupExist ( group ) )
{
// add all songs in group
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & vSongs = GetSongs ( group ) ;
2013-04-30 19:28:28 -04:00
for ( Song * song : vSongs )
2012-06-27 20:25:59 -05:00
{
2013-04-30 19:28:28 -04:00
if ( UNLOCKMAN - > SongIsLocked ( song ) & LOCKED_SELECTABLE )
2012-06-27 20:25:59 -05:00
continue ;
2013-04-30 19:28:28 -04:00
section . vpSongs . push_back ( song ) ;
2012-06-27 20:25:59 -05:00
}
}
}
Song * pSong = FindSong ( sLine ) ;
2013-05-03 23:11:42 -04:00
if ( pSong = = nullptr )
2012-06-27 20:25:59 -05:00
continue ;
if ( UNLOCKMAN - > SongIsLocked ( pSong ) & LOCKED_SELECTABLE )
continue ;
section . vpSongs . push_back ( pSong ) ;
}
}
if ( ! section . vpSongs . empty ( ) )
{
m_vPreferredSongSort . push_back ( section ) ;
section = PreferredSortSection ( ) ;
}
if ( MOVE_UNLOCKS_TO_BOTTOM_OF_PREFERRED_SORT . GetValue ( ) )
{
// move all unlock songs to a group at the bottom
PreferredSortSection PFSection ;
PFSection . sName = " Unlocks " ;
2013-05-01 23:00:25 -04:00
for ( UnlockEntry const & ue : UNLOCKMAN - > m_UnlockEntries )
2012-06-27 20:25:59 -05:00
{
2013-05-01 23:00:25 -04:00
if ( ue . m_Type = = UnlockRewardType_Song )
2012-06-27 20:25:59 -05:00
{
2013-05-01 23:00:25 -04:00
Song * pSong = ue . m_Song . ToSong ( ) ;
2012-06-27 20:25:59 -05:00
if ( pSong )
PFSection . vpSongs . push_back ( pSong ) ;
}
}
2022-07-10 18:28:56 +03:00
for ( std : : vector < PreferredSortSection > : : iterator v = m_vPreferredSongSort . begin ( ) ; v ! = m_vPreferredSongSort . end ( ) ; + + v )
2012-06-27 20:25:59 -05:00
{
for ( int i = v - > vpSongs . size ( ) - 1 ; i > = 0 ; i - - )
{
Song * pSong = v - > vpSongs [ i ] ;
if ( find ( PFSection . vpSongs . begin ( ) , PFSection . vpSongs . end ( ) , pSong ) ! = PFSection . vpSongs . end ( ) )
{
v - > vpSongs . erase ( v - > vpSongs . begin ( ) + i ) ;
}
}
}
m_vPreferredSongSort . push_back ( PFSection ) ;
}
// prune empty groups
for ( int i = m_vPreferredSongSort . size ( ) - 1 ; i > = 0 ; i - - )
if ( m_vPreferredSongSort [ i ] . vpSongs . empty ( ) )
m_vPreferredSongSort . erase ( m_vPreferredSongSort . begin ( ) + i ) ;
2013-05-01 23:00:25 -04:00
for ( PreferredSortSection const & i : m_vPreferredSongSort )
for ( Song const * j : i . vpSongs )
{
2013-05-03 23:01:54 -04:00
ASSERT ( j ! = nullptr ) ;
2013-05-01 23:00:25 -04:00
}
2012-06-27 20:25:59 -05:00
}
{
m_vPreferredCourseSort . clear ( ) ;
2022-07-10 18:28:56 +03:00
std : : vector < RString > asLines ;
2012-06-27 20:25:59 -05:00
RString sFile = THEME - > GetPathO ( " SongManager " , sPreferredCourses ) ;
if ( ! GetFileContents ( sFile , asLines ) )
return ;
2022-07-10 18:28:56 +03:00
std : : vector < Course * > vpCourses ;
2012-06-27 20:25:59 -05:00
2013-05-01 23:00:25 -04:00
for ( RString sLine : asLines )
2012-06-27 20:25:59 -05:00
{
bool bSectionDivider = BeginsWith ( sLine , " --- " ) ;
if ( bSectionDivider )
{
if ( ! vpCourses . empty ( ) )
{
m_vPreferredCourseSort . push_back ( vpCourses ) ;
vpCourses . clear ( ) ;
}
continue ;
}
Course * pCourse = FindCourse ( sLine ) ;
2013-05-03 23:11:42 -04:00
if ( pCourse = = nullptr )
2012-06-27 20:25:59 -05:00
continue ;
if ( UNLOCKMAN - > CourseIsLocked ( pCourse ) & LOCKED_SELECTABLE )
continue ;
vpCourses . push_back ( pCourse ) ;
}
if ( ! vpCourses . empty ( ) )
{
m_vPreferredCourseSort . push_back ( vpCourses ) ;
vpCourses . clear ( ) ;
}
if ( MOVE_UNLOCKS_TO_BOTTOM_OF_PREFERRED_SORT . GetValue ( ) )
{
// move all unlock Courses to a group at the bottom
2022-07-10 18:28:56 +03:00
std : : vector < Course * > vpUnlockCourses ;
2013-05-01 23:00:25 -04:00
for ( UnlockEntry const & ue : UNLOCKMAN - > m_UnlockEntries )
2012-06-27 20:25:59 -05:00
{
2013-05-01 23:00:25 -04:00
if ( ue . m_Type = = UnlockRewardType_Course )
if ( ue . m_Course . IsValid ( ) )
vpUnlockCourses . push_back ( ue . m_Course . ToCourse ( ) ) ;
2012-06-27 20:25:59 -05:00
}
2013-05-01 23:00:25 -04:00
for ( auto v = m_vPreferredCourseSort . begin ( ) ; v ! = m_vPreferredCourseSort . end ( ) ; + + v )
2012-06-27 20:25:59 -05:00
{
for ( int i = v - > size ( ) - 1 ; i > = 0 ; i - - )
{
Course * pCourse = ( * v ) [ i ] ;
if ( find ( vpUnlockCourses . begin ( ) , vpUnlockCourses . end ( ) , pCourse ) ! = vpUnlockCourses . end ( ) )
{
v - > erase ( v - > begin ( ) + i ) ;
}
}
}
m_vPreferredCourseSort . push_back ( vpUnlockCourses ) ;
}
// prune empty groups
for ( int i = m_vPreferredCourseSort . size ( ) - 1 ; i > = 0 ; i - - )
if ( m_vPreferredCourseSort [ i ] . empty ( ) )
m_vPreferredCourseSort . erase ( m_vPreferredCourseSort . begin ( ) + i ) ;
2013-05-01 23:00:25 -04:00
for ( CoursePointerVector const & i : m_vPreferredCourseSort )
for ( Course * j : i )
{
2013-05-03 23:01:54 -04:00
ASSERT ( j ! = nullptr ) ;
2013-05-01 23:00:25 -04:00
}
2012-06-27 20:25:59 -05:00
}
}
void SongManager : : SortSongs ( )
{
SongUtil : : SortSongPointerArrayByTitle ( m_pSongs ) ;
}
void SongManager : : UpdateRankingCourses ( )
{
/* Updating the ranking courses data is fairly expensive since it involves
* comparing strings. Do so sparingly. */
2022-07-10 18:28:56 +03:00
std : : vector < RString > RankingCourses ;
2012-06-27 20:25:59 -05:00
split ( THEME - > GetMetric ( " ScreenRanking " , " CoursesToShow " ) , " , " , RankingCourses ) ;
2013-05-01 23:00:25 -04:00
for ( Course * c : m_pCourses )
2012-06-27 20:25:59 -05:00
{
2013-05-01 23:00:25 -04:00
bool bLotsOfStages = c - > GetEstimatedNumStages ( ) > 7 ;
c - > m_SortOrder_Ranking = bLotsOfStages ? 3 : 2 ;
2012-06-27 20:25:59 -05:00
for ( unsigned j = 0 ; j < RankingCourses . size ( ) ; j + + )
2013-05-01 23:00:25 -04:00
if ( ! RankingCourses [ j ] . CompareNoCase ( c - > m_sPath ) )
c - > m_SortOrder_Ranking = 1 ;
2012-06-27 20:25:59 -05:00
}
}
void SongManager : : RefreshCourseGroupInfo ( )
{
m_mapCourseGroupToInfo . clear ( ) ;
2013-04-28 16:28:44 -04:00
for ( Course const * c : m_pCourses )
2012-06-27 20:25:59 -05:00
{
2013-04-28 16:28:44 -04:00
m_mapCourseGroupToInfo [ c - > m_sGroupName ] ; // insert
2012-06-27 20:25:59 -05:00
}
}
void SongManager : : LoadStepEditsFromProfileDir ( const RString & sProfileDir , ProfileSlot slot )
{
2013-11-14 12:03:15 -06:00
// Load all edit steps
RString sDir = sProfileDir + EDIT_STEPS_SUBDIR ;
2013-11-14 13:29:00 -06:00
SSCLoader loaderSSC ;
SMLoader loaderSM ;
int iNumEditsLoaded = GetNumEditsLoadedFromProfile ( slot ) ;
2012-06-27 20:25:59 -05:00
2013-11-14 13:29:00 -06:00
// Pass 1: Flat folder (old style)
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsFiles ;
int size = std : : min ( ( int ) vsFiles . size ( ) , MAX_EDIT_STEPS_PER_PROFILE - iNumEditsLoaded ) ;
2013-11-14 13:29:00 -06:00
GetDirListing ( sDir + " *.edit " , vsFiles , false , true ) ;
// XXX: If some edits are invalid and they're close to the edit limit, this may erroneously skip some edits, and won't warn.
2013-11-14 12:03:15 -06:00
for ( int i = 0 ; i < size ; i + + )
{
RString fn = vsFiles [ i ] ;
bool bLoadedFromSSC = loaderSSC . LoadEditFromFile ( fn , slot , true ) ;
// If we don't load the edit from a .ssc-style .edit, then we should
// also try the .sm-style edit file. -aj
if ( ! bLoadedFromSSC )
2012-06-27 20:25:59 -05:00
{
2013-11-14 12:03:15 -06:00
loaderSM . LoadEditFromFile ( fn , slot , true ) ;
2012-06-27 20:25:59 -05:00
}
}
2013-11-14 13:29:00 -06:00
2014-02-08 19:59:10 -05:00
if ( ( int ) vsFiles . size ( ) > MAX_EDIT_STEPS_PER_PROFILE - iNumEditsLoaded )
2013-11-14 13:29:00 -06:00
{
2014-07-07 00:39:27 -06:00
LuaHelpers : : ReportScriptErrorFmt ( " Profile %s has too many edits; some have been skipped. " , ProfileSlotToString ( slot ) . c_str ( ) ) ;
2013-11-14 13:29:00 -06:00
return ;
}
// Some .edit files may have been invalid, so re-query instead of just += size.
iNumEditsLoaded = GetNumEditsLoadedFromProfile ( slot ) ;
// Pass 2: Group and song folders with #SONG inferred from folder (optional new style)
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsGroups ;
2013-11-14 13:29:00 -06:00
GetDirListing ( sDir + " * " , vsGroups , true , false ) ;
// XXX: Same as above, edits may be skipped in error in some cases
2014-02-08 19:59:10 -05:00
for ( unsigned i = 0 ; i < vsGroups . size ( ) ; i + + )
2013-11-14 13:29:00 -06:00
{
RString sGroupDir = vsGroups [ i ] + " / " ;
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsSongs ;
2013-11-14 13:29:00 -06:00
GetDirListing ( sDir + sGroupDir + " * " , vsSongs , true , false ) ;
2014-02-08 19:59:10 -05:00
for ( unsigned j = 0 ; j < vsSongs . size ( ) ; j + + )
2013-11-14 13:29:00 -06:00
{
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsEdits ;
2013-11-14 13:29:00 -06:00
RString sSongDir = sGroupDir + vsSongs [ j ] + " / " ;
// XXX There doesn't appear to be a songdir const?
Song * given = GetSongFromDir ( " /Songs/ " + sSongDir ) ;
// NOTE: We don't have to check the return value of GetSongFromDir here,
2019-06-22 12:35:38 -07:00
// because if it fails, it returns nullptr, which is then passed to NotesLoader*.LoadEditFromFile(),
2013-11-14 13:29:00 -06:00
// which will interpret that as "we couldn't infer the song from the path",
// which is what we want in that case anyway.
GetDirListing ( sDir + sSongDir + " /*.edit " , vsEdits , false , true ) ;
2022-07-10 18:28:56 +03:00
size = std : : min ( ( int ) vsEdits . size ( ) , MAX_EDIT_STEPS_PER_PROFILE - iNumEditsLoaded ) ;
2013-11-14 13:29:00 -06:00
for ( int k = 0 ; k < size ; k + + )
{
RString fn = vsEdits [ k ] ;
bool bLoadedFromSSC = loaderSSC . LoadEditFromFile ( fn , slot , true , given ) ;
// And try again with SM
if ( ! bLoadedFromSSC )
loaderSM . LoadEditFromFile ( fn , slot , true , given ) ;
}
2014-02-08 19:59:10 -05:00
if ( ( int ) vsEdits . size ( ) > MAX_EDIT_STEPS_PER_PROFILE - iNumEditsLoaded )
2013-11-14 13:29:00 -06:00
{
2014-07-07 00:39:27 -06:00
LuaHelpers : : ReportScriptErrorFmt ( " Profile %s has too many edits; some have been skipped. " , ProfileSlotToString ( slot ) . c_str ( ) ) ;
2013-11-14 13:29:00 -06:00
return ;
}
// Some .edit files may have been invalid, so re-query instead of just += size.
iNumEditsLoaded = GetNumEditsLoadedFromProfile ( slot ) ;
}
}
2012-06-27 20:25:59 -05:00
}
void SongManager : : LoadCourseEditsFromProfileDir ( const RString & sProfileDir , ProfileSlot slot )
{
2013-11-14 12:03:15 -06:00
// Load all edit courses
RString sDir = sProfileDir + EDIT_COURSES_SUBDIR ;
2012-06-27 20:25:59 -05:00
2022-07-10 18:28:56 +03:00
std : : vector < RString > vsFiles ;
2013-11-14 12:03:15 -06:00
GetDirListing ( sDir + " *.crs " , vsFiles , false , true ) ;
2012-06-27 20:25:59 -05:00
2013-11-14 12:03:15 -06:00
int iNumEditsLoaded = GetNumEditsLoadedFromProfile ( slot ) ;
2022-07-10 18:28:56 +03:00
int size = std : : min ( ( int ) vsFiles . size ( ) , MAX_EDIT_COURSES_PER_PROFILE - iNumEditsLoaded ) ;
2012-06-27 20:25:59 -05:00
2013-11-14 12:03:15 -06:00
for ( int i = 0 ; i < size ; i + + )
{
RString fn = vsFiles [ i ] ;
2012-06-27 20:25:59 -05:00
2013-11-14 12:03:15 -06:00
CourseLoaderCRS : : LoadEditFromFile ( fn , slot ) ;
2012-06-27 20:25:59 -05:00
}
}
int SongManager : : GetNumEditsLoadedFromProfile ( ProfileSlot slot ) const
{
int iCount = 0 ;
for ( unsigned s = 0 ; s < m_pSongs . size ( ) ; s + + )
{
const Song * pSong = m_pSongs [ s ] ;
2022-07-10 18:28:56 +03:00
std : : vector < Steps * > apSteps ;
2012-06-27 20:25:59 -05:00
SongUtil : : GetSteps ( pSong , apSteps ) ;
for ( unsigned i = 0 ; i < apSteps . size ( ) ; + + i )
{
const Steps * pSteps = apSteps [ i ] ;
if ( pSteps - > WasLoadedFromProfile ( ) & & pSteps - > GetLoadedFromProfileSlot ( ) = = slot )
+ + iCount ;
}
}
return iCount ;
}
2015-03-30 17:45:52 -06:00
void SongManager : : AddSongToList ( Song * new_song )
{
new_song - > SetEnabled ( true ) ;
m_pSongs . push_back ( new_song ) ;
RString dir = new_song - > GetSongDir ( ) ;
dir . MakeLower ( ) ;
m_SongsByDir . insert ( make_pair ( dir , new_song ) ) ;
}
2012-06-27 20:25:59 -05:00
void SongManager : : FreeAllLoadedFromProfile ( ProfileSlot slot )
{
// Profile courses may refer to profile steps, so free profile courses first.
2022-07-10 18:28:56 +03:00
std : : vector < Course * > apToDelete ;
2013-05-01 23:00:25 -04:00
for ( Course * pCourse : m_pCourses )
2012-06-27 20:25:59 -05:00
{
if ( pCourse - > GetLoadedFromProfileSlot ( ) = = ProfileSlot_Invalid )
continue ;
if ( slot = = ProfileSlot_Invalid | | pCourse - > GetLoadedFromProfileSlot ( ) = = slot )
2013-05-01 23:00:25 -04:00
apToDelete . push_back ( pCourse ) ;
2012-06-27 20:25:59 -05:00
}
/* We don't use DeleteCourse here, so we don't UpdatePopular and
* UpdateShuffled repeatedly. */
for ( unsigned i = 0 ; i < apToDelete . size ( ) ; + + i )
{
2022-07-10 18:28:56 +03:00
std : : vector < Course * > : : iterator iter = find ( m_pCourses . begin ( ) , m_pCourses . end ( ) , apToDelete [ i ] ) ;
2012-06-27 20:25:59 -05:00
ASSERT ( iter ! = m_pCourses . end ( ) ) ;
m_pCourses . erase ( iter ) ;
delete apToDelete [ i ] ;
}
// Popular and Shuffled may refer to courses that we just freed.
UpdatePopular ( ) ;
UpdateShuffled ( ) ;
RefreshCourseGroupInfo ( ) ;
// Free profile steps.
2022-07-10 18:28:56 +03:00
std : : set < Steps * > setInUse ;
2012-06-27 20:25:59 -05:00
if ( STATSMAN )
STATSMAN - > GetStepsInUse ( setInUse ) ;
2013-05-01 23:00:25 -04:00
for ( Song * s : m_pSongs )
s - > FreeAllLoadedFromProfile ( slot , & setInUse ) ;
2012-06-27 20:25:59 -05:00
}
int SongManager : : GetNumStepsLoadedFromProfile ( )
{
int iCount = 0 ;
2013-05-01 23:00:25 -04:00
for ( Song const * s : m_pSongs )
2012-06-27 20:25:59 -05:00
{
2022-07-10 18:28:56 +03:00
std : : vector < Steps * > vpAllSteps = s - > GetAllSteps ( ) ;
2012-06-27 20:25:59 -05:00
2013-05-01 23:00:25 -04:00
iCount + = std : : count_if ( vpAllSteps . begin ( ) , vpAllSteps . end ( ) , [ ] ( Steps const * step ) {
return step - > GetLoadedFromProfileSlot ( ) ! = ProfileSlot_Invalid ;
} ) ;
2012-06-27 20:25:59 -05:00
}
return iCount ;
}
template < class T >
int FindCourseIndexOfSameMode ( T begin , T end , const Course * p )
{
const PlayMode pm = p - > GetPlayMode ( ) ;
int n = 0 ;
for ( T it = begin ; it ! = end ; + + it )
{
if ( * it = = p )
return n ;
/* If it's not playable in this mode, don't increment. It might result in
* different output in different modes, but that's better than having holes. */
2014-12-07 01:53:17 -07:00
if ( ! ( * it ) - > IsPlayableIn ( GAMESTATE - > GetCurrentStyle ( GAMESTATE - > GetMasterPlayerNumber ( ) ) - > m_StepsType ) )
2012-06-27 20:25:59 -05:00
continue ;
if ( ( * it ) - > GetPlayMode ( ) ! = pm )
continue ;
+ + n ;
}
return - 1 ;
}
int SongManager : : GetSongRank ( Song * pSong )
{
const int index = FindIndex ( m_pPopularSongs . begin ( ) , m_pPopularSongs . end ( ) , pSong ) ;
return index ; // -1 means we didn't find it
}
// lua start
# include "LuaBinding.h"
/** @brief Allow Lua to have access to the SongManager. */
class LunaSongManager : public Luna < SongManager >
{
public :
static int SetPreferredSongs ( T * p , lua_State * L )
{
p - > UpdatePreferredSort ( SArg ( 1 ) , " PreferredCourses.txt " ) ;
2014-12-01 22:31:30 -07:00
COMMON_RETURN_SELF ;
2012-06-27 20:25:59 -05:00
}
static int SetPreferredCourses ( T * p , lua_State * L )
{
p - > UpdatePreferredSort ( " PreferredSongs.txt " , SArg ( 1 ) ) ;
2014-12-01 22:31:30 -07:00
COMMON_RETURN_SELF ;
2012-06-27 20:25:59 -05:00
}
static int GetAllSongs ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & v = p - > GetAllSongs ( ) ;
2012-06-27 20:25:59 -05:00
LuaHelpers : : CreateTableFromArray < Song * > ( v , L ) ;
return 1 ;
}
static int GetAllCourses ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
std : : vector < Course * > v ;
2012-06-27 20:25:59 -05:00
p - > GetAllCourses ( v , BArg ( 1 ) ) ;
LuaHelpers : : CreateTableFromArray < Course * > ( v , L ) ;
return 1 ;
}
static int GetPreferredSortSongs ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
std : : vector < Song * > v ;
2012-06-27 20:25:59 -05:00
p - > GetPreferredSortSongs ( v ) ;
LuaHelpers : : CreateTableFromArray < Song * > ( v , L ) ;
return 1 ;
}
static int GetPreferredSortCourses ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
std : : vector < Course * > v ;
2012-06-27 20:25:59 -05:00
CourseType ct = Enum : : Check < CourseType > ( L , 1 ) ;
p - > GetPreferredSortCourses ( ct , v , BArg ( 2 ) ) ;
LuaHelpers : : CreateTableFromArray < Course * > ( v , L ) ;
return 1 ;
}
static int FindSong ( T * p , lua_State * L ) { Song * pS = p - > FindSong ( SArg ( 1 ) ) ; if ( pS ) pS - > PushSelf ( L ) ; else lua_pushnil ( L ) ; return 1 ; }
static int FindCourse ( T * p , lua_State * L ) { Course * pC = p - > FindCourse ( SArg ( 1 ) ) ; if ( pC ) pC - > PushSelf ( L ) ; else lua_pushnil ( L ) ; return 1 ; }
static int GetRandomSong ( T * p , lua_State * L ) { Song * pS = p - > GetRandomSong ( ) ; if ( pS ) pS - > PushSelf ( L ) ; else lua_pushnil ( L ) ; return 1 ; }
static int GetRandomCourse ( T * p , lua_State * L ) { Course * pC = p - > GetRandomCourse ( ) ; if ( pC ) pC - > PushSelf ( L ) ; else lua_pushnil ( L ) ; return 1 ; }
static int GetNumSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , p - > GetNumSongs ( ) ) ; return 1 ; }
static int GetNumLockedSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , p - > GetNumLockedSongs ( ) ) ; return 1 ; }
static int GetNumUnlockedSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , p - > GetNumUnlockedSongs ( ) ) ; return 1 ; }
static int GetNumSelectableAndUnlockedSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , p - > GetNumSelectableAndUnlockedSongs ( ) ) ; return 1 ; }
2022-02-19 22:07:36 +01:00
static int GetNumAdditionalSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , 0 ) ; return 1 ; } // deprecated
2012-06-27 20:25:59 -05:00
static int GetNumSongGroups ( T * p , lua_State * L ) { lua_pushnumber ( L , p - > GetNumSongGroups ( ) ) ; return 1 ; }
static int GetNumCourses ( T * p , lua_State * L ) { lua_pushnumber ( L , p - > GetNumCourses ( ) ) ; return 1 ; }
2022-02-19 22:07:36 +01:00
static int GetNumAdditionalCourses ( T * p , lua_State * L ) { lua_pushnumber ( L , 0 ) ; return 1 ; } // deprecated
2012-06-27 20:25:59 -05:00
static int GetNumCourseGroups ( T * p , lua_State * L ) { lua_pushnumber ( L , p - > GetNumCourseGroups ( ) ) ; return 1 ; }
2022-02-19 22:07:36 +01:00
2013-01-20 19:51:59 -05:00
/* Note: this could now be implemented as Luna<Steps>::GetSong */
2012-06-27 20:25:59 -05:00
static int GetSongFromSteps ( T * p , lua_State * L )
{
2013-05-03 23:39:52 -04:00
Song * pSong = nullptr ;
if ( lua_isnil ( L , 1 ) ) { pSong = nullptr ; }
2013-01-20 19:51:59 -05:00
else { Steps * pSteps = Luna < Steps > : : check ( L , 1 ) ; pSong = pSteps - > m_pSong ; }
2012-06-27 20:25:59 -05:00
if ( pSong ) pSong - > PushSelf ( L ) ;
else lua_pushnil ( L ) ;
return 1 ;
}
static int GetExtraStageInfo ( T * p , lua_State * L )
{
bool bExtra2 = BArg ( 1 ) ;
const Style * pStyle = Luna < Style > : : check ( L , 2 ) ;
Song * pSong ;
Steps * pSteps ;
p - > GetExtraStageInfo ( bExtra2 , pStyle , pSong , pSteps ) ;
pSong - > PushSelf ( L ) ;
pSteps - > PushSelf ( L ) ;
return 2 ;
}
DEFINE_METHOD ( GetSongColor , GetSongColor ( Luna < Song > : : check ( L , 1 ) ) )
DEFINE_METHOD ( GetSongGroupColor , GetSongGroupColor ( SArg ( 1 ) ) )
DEFINE_METHOD ( GetCourseColor , GetCourseColor ( Luna < Course > : : check ( L , 1 ) ) )
static int GetSongRank ( T * p , lua_State * L )
{
Song * pSong = Luna < Song > : : check ( L , 1 ) ;
int index = p - > GetSongRank ( pSong ) ;
if ( index ! = - 1 )
lua_pushnumber ( L , index + 1 ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
/*
static int GetSongRankFromProfile( T* p, lua_State *L )
{
// it's like the above but also takes in a ProfileSlot as well.
}
*/
static int GetSongGroupNames ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
std : : vector < RString > v ;
2012-06-27 20:25:59 -05:00
p - > GetSongGroupNames ( v ) ;
LuaHelpers : : CreateTableFromArray < RString > ( v , L ) ;
return 1 ;
}
static int GetSongsInGroup ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
std : : vector < Song * > v = p - > GetSongs ( SArg ( 1 ) ) ;
2012-06-27 20:25:59 -05:00
LuaHelpers : : CreateTableFromArray < Song * > ( v , L ) ;
return 1 ;
}
static int GetCoursesInGroup ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
std : : vector < Course * > v ;
2012-06-27 20:25:59 -05:00
p - > GetCoursesInGroup ( v , SArg ( 1 ) , BArg ( 2 ) ) ;
LuaHelpers : : CreateTableFromArray < Course * > ( v , L ) ;
return 1 ;
}
DEFINE_METHOD ( ShortenGroupName , ShortenGroupName ( SArg ( 1 ) ) )
static int GetCourseGroupNames ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
std : : vector < RString > v ;
2012-06-27 20:25:59 -05:00
p - > GetCourseGroupNames ( v ) ;
LuaHelpers : : CreateTableFromArray < RString > ( v , L ) ;
return 1 ;
}
DEFINE_METHOD ( GetSongGroupBannerPath , GetSongGroupBannerPath ( SArg ( 1 ) ) ) ;
DEFINE_METHOD ( GetCourseGroupBannerPath , GetCourseGroupBannerPath ( SArg ( 1 ) ) ) ;
DEFINE_METHOD ( DoesSongGroupExist , DoesSongGroupExist ( SArg ( 1 ) ) ) ;
DEFINE_METHOD ( DoesCourseGroupExist , DoesCourseGroupExist ( SArg ( 1 ) ) ) ;
static int GetPopularSongs ( T * p , lua_State * L )
{
2022-07-10 18:28:56 +03:00
const std : : vector < Song * > & v = p - > GetPopularSongs ( ) ;
2012-06-27 20:25:59 -05:00
LuaHelpers : : CreateTableFromArray < Song * > ( v , L ) ;
return 1 ;
}
static int GetPopularCourses ( T * p , lua_State * L )
{
CourseType ct = Enum : : Check < CourseType > ( L , 1 ) ;
2022-07-10 18:28:56 +03:00
const std : : vector < Course * > & v = p - > GetPopularCourses ( ct ) ;
2012-06-27 20:25:59 -05:00
LuaHelpers : : CreateTableFromArray < Course * > ( v , L ) ;
return 1 ;
}
static int SongToPreferredSortSectionName ( T * p , lua_State * L )
{
const Song * pSong = Luna < Song > : : check ( L , 1 ) ;
lua_pushstring ( L , p - > SongToPreferredSortSectionName ( pSong ) ) ;
return 1 ;
}
2022-02-19 22:07:36 +01:00
static int WasLoadedFromAdditionalSongs ( T * p , lua_State * L ) { lua_pushboolean ( L , false ) ; return 1 ; } // deprecated
static int WasLoadedFromAdditionalCourses ( T * p , lua_State * L ) { lua_pushboolean ( L , false ) ; return 1 ; } // deprecated
2012-06-27 20:25:59 -05:00
LunaSongManager ( )
{
ADD_METHOD ( GetAllSongs ) ;
ADD_METHOD ( GetAllCourses ) ;
ADD_METHOD ( FindSong ) ;
ADD_METHOD ( FindCourse ) ;
ADD_METHOD ( GetRandomSong ) ;
ADD_METHOD ( GetRandomCourse ) ;
ADD_METHOD ( GetCourseGroupNames ) ;
ADD_METHOD ( GetNumSongs ) ;
ADD_METHOD ( GetNumLockedSongs ) ;
ADD_METHOD ( GetNumUnlockedSongs ) ;
ADD_METHOD ( GetNumSelectableAndUnlockedSongs ) ;
2022-02-19 22:07:36 +01:00
ADD_METHOD ( GetNumAdditionalSongs ) ; // deprecated
2012-06-27 20:25:59 -05:00
ADD_METHOD ( GetNumSongGroups ) ;
ADD_METHOD ( GetNumCourses ) ;
2022-02-19 22:07:36 +01:00
ADD_METHOD ( GetNumAdditionalCourses ) ; // deprecated
2012-06-27 20:25:59 -05:00
ADD_METHOD ( GetNumCourseGroups ) ;
ADD_METHOD ( GetSongFromSteps ) ;
ADD_METHOD ( GetExtraStageInfo ) ;
ADD_METHOD ( GetSongColor ) ;
ADD_METHOD ( GetSongGroupColor ) ;
ADD_METHOD ( GetCourseColor ) ;
ADD_METHOD ( GetSongRank ) ;
ADD_METHOD ( GetSongGroupNames ) ;
ADD_METHOD ( GetSongsInGroup ) ;
ADD_METHOD ( GetCoursesInGroup ) ;
ADD_METHOD ( ShortenGroupName ) ;
ADD_METHOD ( SetPreferredSongs ) ;
ADD_METHOD ( SetPreferredCourses ) ;
ADD_METHOD ( GetPreferredSortSongs ) ;
ADD_METHOD ( GetPreferredSortCourses ) ;
ADD_METHOD ( GetSongGroupBannerPath ) ;
ADD_METHOD ( GetCourseGroupBannerPath ) ;
ADD_METHOD ( DoesSongGroupExist ) ;
ADD_METHOD ( DoesCourseGroupExist ) ;
ADD_METHOD ( GetPopularSongs ) ;
ADD_METHOD ( GetPopularCourses ) ;
ADD_METHOD ( SongToPreferredSortSectionName ) ;
2022-02-19 22:07:36 +01:00
ADD_METHOD ( WasLoadedFromAdditionalSongs ) ; // deprecated
ADD_METHOD ( WasLoadedFromAdditionalCourses ) ; // deprecated
2012-06-27 20:25:59 -05:00
}
} ;
LUA_REGISTER_CLASS ( SongManager )
// lua end
/*
* (c) 2001-2004 Chris Danford, Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/