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"
2023-04-19 23:04:25 +02:00
#include <cstddef>
2022-03-18 21:55:07 +01:00
#include <tuple>
2023-04-20 19:02:13 +02:00
#include <vector>
2022-03-18 21:55:07 +01:00
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 );
2023-04-19 23:04:25 +02:00
RString SONG_GROUP_COLOR_NAME ( std :: size_t i ) { return ssprintf ( "SongGroupColor%i" , ( int ) i + 1 ); }
RString COURSE_GROUP_COLOR_NAME ( std :: size_t i ) { return ssprintf ( "CourseGroupColor%i" , ( int ) i + 1 ); }
RString profile_song_group_color_name ( std :: 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 ();
2023-12-12 17:56:44 -05:00
UpdateMeterSort ();
2012-06-27 20:25:59 -05:00
}
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 ();
2023-12-12 17:56:44 -05:00
UpdateMeterSort ();
2022-06-09 15:31:29 +02:00
}
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];
}
*/
/*
2023-04-19 23:04:25 +02:00
LOG->Trace( "Group banner for '%s' is '%s'.", sGroupDirName.c_str(),
2012-06-27 20:25:59 -05:00
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
2023-04-19 23:04:25 +02:00
for ( unsigned i = 0 ; i < m_pDeletedSongs . size (); ++ i )
2015-03-17 04:20:02 +00:00
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 ];
2023-04-19 23:04:25 +02:00
for ( std :: size_t i = 0 ; i < v . size (); ++ i ) {
2023-03-17 19:59:28 +01:00
if ( v [ i ] == song ) {
v . erase ( v . begin () + i );
-- i ;
}
}
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 )
{
2023-04-19 23:04:25 +02:00
if ( sSongGroup == m_sSongGroupNames [ i ] )
2012-06-27 20:25:59 -05:00
return m_sSongGroupBannerPaths [ i ];
}
return RString ();
}
/*
RString SongManager::GetSongGroupBackgroundPath( RString sSongGroup ) const
{
for( unsigned i = 0; i < m_sSongGroupNames.size(); ++i )
{
2023-04-19 23:04:25 +02:00
if( sSongGroup == m_sSongGroupNames[i] )
2012-06-27 20:25:59 -05:00
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
2023-04-19 23:04:25 +02:00
* in the song scroll, which is a little odd but harmless.
2012-06-27 20:25:59 -05:00
*
* 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 ();
}
2023-04-19 23:04:25 +02:00
else
2012-06-27 20:25:59 -05:00
{
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 ();
}
2023-09-26 22:12:31 -04:00
void SongManager :: GetPreferredSortSongsBySectionName ( const RString & sSectionName , std :: vector < Song *> & AddTo ) const
{
2024-02-25 11:12:46 -05:00
// Use m_mapPreferredSectionToSongs
std :: map < RString , SongPointerVector >:: const_iterator iter = m_mapPreferredSectionToSongs . find ( sSectionName );
if ( iter != m_mapPreferredSectionToSongs . end () )
AddTo . insert ( AddTo . end (), iter -> second . begin (), iter -> second . end () );
2023-09-26 22:12:31 -04:00
}
std :: vector < Song *> SongManager :: GetPreferredSortSongsBySectionName ( const RString & sSectionName ) const
{
std :: vector < Song *> AddTo ;
2024-02-25 09:52:23 -05:00
GetPreferredSortSongsBySectionName ( sSectionName , AddTo );
2023-09-26 22:12:31 -04:00
return AddTo ;
}
std :: vector < RString > SongManager :: GetPreferredSortSectionNames () const
{
std :: vector < RString > sectionNames ;
2024-02-25 11:12:46 -05:00
// Use m_mapPreferredSectionToSongs
for ( std :: pair < RString const , SongPointerVector > const & iter : m_mapPreferredSectionToSongs )
sectionNames . push_back ( iter . first );
2023-09-26 22:12:31 -04:00
return sectionNames ;
2024-02-25 11:12:46 -05:00
2023-09-26 22:12:31 -04:00
}
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 );
}
}
}
2023-12-12 17:56:44 -05:00
std :: vector < Song *> SongManager :: GetSongsByMeter ( const int iMeter ) const {
std :: vector < Song *> AddTo ;
auto iter = m_mapSongsByDifficulty . find ( iMeter );
if ( iter != m_mapSongsByDifficulty . end ()) {
// Found the level, add the songs to AddTo
AddTo . insert ( AddTo . end (), iter -> second . begin (), iter -> second . end ());
}
return AddTo ;
}
2012-06-27 20:25:59 -05:00
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 ();
2023-12-12 17:56:44 -05:00
UpdateMeterSort ();
2012-06-27 20:25:59 -05:00
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 ;
2023-04-19 23:04:25 +02:00
// n1 difficulty == n2 difficulty
2012-06-27 20:25:59 -05:00
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 ;
2023-04-19 23:04:25 +02: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)
2023-04-19 23:04:25 +02:00
if ( bExtra2 && pSteps -> GetMeter () > EXTRA_STAGE2_DIFFICULTY_MAX )
2012-06-27 20:25:59 -05:00
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:
2023-04-19 23:04:25 +02:00
* Songs\Group\SongName or
2012-06-27 20:25:59 -05:00
* 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 );
}
}
2023-12-12 17:56:44 -05:00
void SongManager :: UpdateMeterSort () {
std :: vector < Song *> apDifficultSongs = m_pSongs ;
// For each song, for each step
for ( unsigned i = 0 ; i < apDifficultSongs . size (); ++ i )
{
const std :: vector < Steps *> & vSteps = apDifficultSongs [ i ] -> GetAllSteps ();
for ( unsigned j = 0 ; j < vSteps . size (); ++ j )
{
Steps * pSteps = vSteps [ j ];
// Check if the meter is already in m_mapSongsByDifficulty
if ( std :: find ( m_mapSongsByDifficulty [ pSteps -> GetMeter ()]. begin (), m_mapSongsByDifficulty [ pSteps -> GetMeter ()]. end (), apDifficultSongs [ i ]) != m_mapSongsByDifficulty [ pSteps -> GetMeter ()]. end ())
continue ;
else {
m_mapSongsByDifficulty [ pSteps -> GetMeter ()]. push_back ( apDifficultSongs [ i ]);
}
}
}
// For each meter in m_mapSongsByDifficulty, sort the songs by title
for ( unsigned i = 0 ; i < m_mapSongsByDifficulty . size (); ++ i )
{
SongUtil :: SortSongPointerArrayByTitle ( m_mapSongsByDifficulty [ i ] );
}
}
2012-06-27 20:25:59 -05:00
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
}
2023-03-14 12:02:21 -07:00
void SongManager :: SetPreferredSongs ( RString sPreferredSongs , bool bIsAbsolute ) {
ASSERT ( UNLOCKMAN != nullptr );
m_vPreferredSongSort . clear ();
2024-02-25 11:12:46 -05:00
m_mapPreferredSectionToSongs . clear ();
2023-03-14 12:02:21 -07:00
std :: vector < RString > asLines ;
RString sFile = sPreferredSongs ;
if ( ! bIsAbsolute )
sFile = THEME -> GetPathO ( "SongManager" , sPreferredSongs );
GetFileContents ( sFile , asLines );
if ( asLines . empty () )
return ;
PreferredSortSection section ;
std :: map < Song * , float > mapSongToPri ;
for ( RString sLine : asLines )
{
bool bSectionDivider = BeginsWith ( sLine , "---" );
if ( bSectionDivider )
{
if ( ! section . vpSongs . empty () )
{
m_vPreferredSongSort . push_back ( section );
2024-02-25 11:12:46 -05:00
m_mapPreferredSectionToSongs [ section . sName ] = section . vpSongs ;
2023-03-14 12:02:21 -07:00
section = PreferredSortSection ();
}
section . sName = sLine . Right ( sLine . length () - RString ( "---" ). length () );
TrimLeft ( section . sName );
TrimRight ( section . sName );
}
else
{
/* if the line ends in slash-star, check if the section exists,
* 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
const std :: vector < Song *> & vSongs = GetSongs ( group );
for ( Song * song : vSongs )
{
if ( UNLOCKMAN -> SongIsLocked ( song ) & LOCKED_SELECTABLE )
continue ;
section . vpSongs . push_back ( song );
}
}
}
Song * pSong = FindSong ( sLine );
if ( pSong == nullptr )
continue ;
if ( UNLOCKMAN -> SongIsLocked ( pSong ) & LOCKED_SELECTABLE )
continue ;
section . vpSongs . push_back ( pSong );
}
}
if ( ! section . vpSongs . empty () )
{
m_vPreferredSongSort . push_back ( section );
2024-02-25 11:12:46 -05:00
m_mapPreferredSectionToSongs [ section . sName ] = section . vpSongs ;
2023-03-14 12:02:21 -07:00
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" ;
for ( UnlockEntry const & ue : UNLOCKMAN -> m_UnlockEntries )
{
if ( ue . m_Type == UnlockRewardType_Song )
{
Song * pSong = ue . m_Song . ToSong ();
if ( pSong )
PFSection . vpSongs . push_back ( pSong );
}
}
2024-02-25 11:12:46 -05:00
// NOTE(crashcringle): This code removed the unlocks from other sections they might have been in.
// This was needed due to the previous 1:1 relationship between songs and section in order for the song to appear in the Unlocks section correctly.
// Commented out for now.
// for (std::vector<PreferredSortSection>::iterator v = m_vPreferredSongSort.begin(); v != m_vPreferredSongSort.end(); ++v)
// {
// 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 );
// }
// }
// }
2023-03-14 12:02:21 -07:00
m_vPreferredSongSort . push_back ( PFSection );
2024-02-25 11:12:46 -05:00
m_mapPreferredSectionToSongs [ PFSection . sName ] = PFSection . vpSongs ;
2023-03-14 12:02:21 -07:00
}
// prune empty groups
for ( int i = m_vPreferredSongSort . size () - 1 ; i >= 0 ; i -- )
2024-02-25 11:12:46 -05:00
if ( m_vPreferredSongSort [ i ]. vpSongs . empty () ) {
2023-03-14 12:02:21 -07:00
m_vPreferredSongSort . erase ( m_vPreferredSongSort . begin () + i );
2024-02-25 11:12:46 -05:00
m_mapPreferredSectionToSongs . erase ( m_vPreferredSongSort [ i ]. sName );
}
2023-03-14 12:02:21 -07:00
for ( PreferredSortSection const & i : m_vPreferredSongSort )
{
for ( Song const * j : i . vpSongs )
{
ASSERT ( j != nullptr );
}
}
}
void SongManager :: SetPreferredCourses ( RString sPreferredCourses , bool bIsAbsolute )
2012-06-27 20:25:59 -05:00
{
2013-05-03 23:01:54 -04:00
ASSERT ( UNLOCKMAN != nullptr );
2012-06-27 20:25:59 -05:00
2023-03-14 12:02:21 -07:00
m_vPreferredCourseSort . clear ();
std :: vector < RString > asLines ;
RString sFile = sPreferredCourses ;
if ( ! bIsAbsolute )
sFile = THEME -> GetPathO ( "SongManager" , sPreferredCourses );
if ( ! GetFileContents ( sFile , asLines ) )
return ;
std :: vector < Course *> vpCourses ;
for ( RString sLine : asLines )
2012-06-27 20:25:59 -05:00
{
2023-03-14 12:02:21 -07:00
bool bSectionDivider = BeginsWith ( sLine , "---" );
if ( bSectionDivider )
2012-06-27 20:25:59 -05:00
{
2023-03-14 12:02:21 -07:00
if ( ! vpCourses . empty () )
2012-06-27 20:25:59 -05:00
{
2023-03-14 12:02:21 -07:00
m_vPreferredCourseSort . push_back ( vpCourses );
vpCourses . clear ();
2012-06-27 20:25:59 -05:00
}
2023-03-14 12:02:21 -07:00
continue ;
2012-06-27 20:25:59 -05:00
}
2023-03-14 12:02:21 -07:00
Course * pCourse = FindCourse ( sLine );
if ( pCourse == nullptr )
continue ;
if ( UNLOCKMAN -> CourseIsLocked ( pCourse ) & LOCKED_SELECTABLE )
continue ;
vpCourses . push_back ( pCourse );
2012-06-27 20:25:59 -05:00
}
2023-03-14 12:02:21 -07:00
if ( ! vpCourses . empty () )
2012-06-27 20:25:59 -05:00
{
2023-03-14 12:02:21 -07:00
m_vPreferredCourseSort . push_back ( vpCourses );
vpCourses . clear ();
2012-06-27 20:25:59 -05:00
}
2023-03-14 12:02:21 -07:00
if ( MOVE_UNLOCKS_TO_BOTTOM_OF_PREFERRED_SORT . GetValue () )
{
// move all unlock Courses to a group at the bottom
std :: vector < Course *> vpUnlockCourses ;
for ( UnlockEntry const & ue : UNLOCKMAN -> m_UnlockEntries )
{
if ( ue . m_Type == UnlockRewardType_Course )
if ( ue . m_Course . IsValid () )
vpUnlockCourses . push_back ( ue . m_Course . ToCourse () );
}
for ( auto v = m_vPreferredCourseSort . begin (); v != m_vPreferredCourseSort . end (); ++ v )
{
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 );
for ( CoursePointerVector const & i : m_vPreferredCourseSort )
{
for ( Course * j : i )
{
ASSERT ( j != nullptr );
}
}
}
void SongManager :: UpdatePreferredSort ( RString sPreferredSongs , RString sPreferredCourses )
{
SetPreferredSongs ( sPreferredSongs );
SetPreferredCourses ( sPreferredCourses );
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 ;
2023-04-19 23:04:25 +02:00
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 ;
2013-11-14 12:03:15 -06:00
int iNumEditsLoaded = GetNumEditsLoadedFromProfile ( slot );
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 );
2023-04-19 23:04:25 +02:00
2013-11-14 13:29:00 -06:00
// 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 ++ )
2012-06-27 20:25:59 -05:00
{
2013-11-14 12:03:15 -06:00
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
}
}
2023-04-19 23:04:25 +02: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 );
2023-04-19 23:04:25 +02:00
2013-11-14 13:29:00 -06:00
// 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 );
2023-04-19 23:04:25 +02:00
2013-11-14 13:29:00 -06:00
// 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 );
2023-04-19 23:04:25 +02:00
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 );
2023-04-19 23:04:25 +02:00
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 );
}
2023-04-19 23:04:25 +02:00
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 ;
}
2023-04-19 23:04:25 +02:00
2013-11-14 13:29:00 -06:00
// 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 ;
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 );
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 );
2013-11-14 12:03:15 -06:00
for ( int i = 0 ; i < size ; i ++ )
2012-06-27 20:25:59 -05:00
{
2013-11-14 12:03:15 -06:00
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 ;
2023-04-19 23:04:25 +02:00
/* If it's not playable in this mode, don't increment. It might result in
2012-06-27 20:25:59 -05:00
* 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"
2023-04-19 23:04:25 +02:00
/** @brief Allow Lua to have access to the SongManager. */
2012-06-27 20:25:59 -05:00
class LunaSongManager : public Luna < SongManager >
{
public :
static int SetPreferredSongs ( T * p , lua_State * L )
{
2023-03-14 12:02:21 -07:00
RString sPreferredSongs = SArg ( 1 );
if ( lua_gettop ( L ) >= 2 && ! lua_isnil ( L , 2 ) )
{
p -> SetPreferredSongs ( sPreferredSongs , BArg ( 2 ) );
}
else
{
p -> SetPreferredSongs ( sPreferredSongs );
}
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 )
{
2023-03-14 12:02:21 -07:00
RString sPreferredCourses = SArg ( 1 );
if ( lua_gettop ( L ) >= 2 && ! lua_isnil ( L , 2 ) )
{
p -> SetPreferredSongs ( sPreferredCourses , BArg ( 2 ) );
}
else
{
p -> SetPreferredSongs ( sPreferredCourses );
}
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 ;
}
2023-09-26 22:12:31 -04:00
static int GetPreferredSortSongsBySectionName ( T * p , lua_State * L )
{
std :: vector < Song *> v ;
p -> GetPreferredSortSongsBySectionName ( SArg ( 1 ), v );
LuaHelpers :: CreateTableFromArray < Song *> ( v , L );
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 );
2023-09-26 22:12:31 -04:00
ADD_METHOD ( GetPreferredSortSongsBySectionName );
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.
2023-04-19 23:04:25 +02:00
*
2012-06-27 20:25:59 -05:00
* 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.
2023-04-19 23:04:25 +02:00
*
2012-06-27 20:25:59 -05:00
* 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.
*/