2003-02-16 04:01:45 +00:00
#include "global.h"
2002-02-28 19:40:40 +00:00
#include "SongManager.h"
2002-11-18 21:37:58 +00:00
#include "arch/LoadingWindow/LoadingWindow.h"
2002-11-11 04:53:31 +00:00
#include "AnnouncerManager.h"
2006-11-29 14:57:21 +00:00
#include "BackgroundUtil.h"
#include "BannerCache.h"
#include "CatalogXml.h"
2007-02-04 04:07:14 +00:00
#include "CommonMetrics.h"
2006-11-29 14:57:21 +00:00
#include "Course.h"
#include "CourseLoaderCRS.h"
#include "CourseUtil.h"
#include "Foreach.h"
2002-11-11 04:53:31 +00:00
#include "GameManager.h"
2006-11-29 14:57:21 +00:00
#include "GameState.h"
#include "LocalizedString.h"
#include "MsdFile.h"
#include "NoteSkinManager.h"
#include "NotesLoaderDWI.h"
2004-02-08 01:05:53 +00:00
#include "NotesLoaderSM.h"
2006-11-29 14:57:21 +00:00
#include "PrefsManager.h"
#include "Profile.h"
#include "ProfileManager.h"
#include "RageFile.h"
#include "RageFileManager.h"
#include "RageLog.h"
2005-07-03 07:50:59 +00:00
#include "song.h"
2004-04-18 07:34:37 +00:00
#include "SongUtil.h"
2006-11-29 14:57:21 +00:00
#include "Sprite.h"
#include "StatsManager.h"
2005-07-03 02:48:38 +00:00
#include "Steps.h"
2004-04-18 07:34:37 +00:00
#include "StepsUtil.h"
2004-09-12 05:56:24 +00:00
#include "Style.h"
2006-11-29 14:57:21 +00:00
#include "ThemeManager.h"
2005-10-06 07:01:58 +00:00
#include "TitleSubstitution.h"
2006-11-29 14:57:21 +00:00
#include "TrailUtil.h"
#include "UnlockManager.h"
2003-03-27 21:23:56 +00:00
2002-05-19 01:59:48 +00:00
SongManager * SONGMAN = NULL ; // global and accessable from anywhere in our program
2002-02-28 19:40:40 +00:00
2006-04-04 21:52:54 +00:00
const RString SONGS_DIR = "/Songs/" ;
const RString ADDITIONAL_SONGS_DIR = "/AdditionalSongs/" ;
const RString COURSES_DIR = "/Courses/" ;
2006-08-14 20:05:35 +00:00
const RString ADDITIONAL_COURSES_DIR = "/AdditionalCourses/" ;
2006-05-03 21:33:19 +00:00
const RString EDIT_SUBDIR = "Edits/" ;
2003-01-22 05:29:27 +00:00
2006-08-07 01:23:59 +00: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" );
2006-08-20 05:55:09 +00:00
static const ThemeMetric < int > EXTRA_STAGE2_DIFFICULTY_MAX ( "SongManager" , "ExtraStage2DifficultyMax" );
2006-08-07 01:23:59 +00:00
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 ); }
2002-04-16 17:31:00 +00:00
2002-02-28 19:40:40 +00:00
2004-05-25 05:52:57 +00:00
SongManager :: SongManager ()
2002-02-28 19:40:40 +00:00
{
2006-09-21 04:42:45 +00:00
// Register with Lua.
{
Lua * L = LUA -> Get ();
lua_pushstring ( L , "SONGMAN" );
this -> PushSelf ( L );
lua_settable ( L , LUA_GLOBALSINDEX );
LUA -> Release ( L );
}
2006-08-07 01:23:59 +00:00
NUM_SONG_GROUP_COLORS . Load ( "SongManager" , "NumSongGroupColors" );
SONG_GROUP_COLOR . Load ( "SongManager" , SONG_GROUP_COLOR_NAME , NUM_SONG_GROUP_COLORS );
NUM_COURSE_GROUP_COLORS . Load ( "SongManager" , "NumCourseGroupColors" );
COURSE_GROUP_COLOR . Load ( "SongManager" , COURSE_GROUP_COLOR_NAME , NUM_COURSE_GROUP_COLORS );
2002-02-28 19:40:40 +00:00
}
SongManager ::~ SongManager ()
{
2006-09-21 04:42:45 +00:00
// Unregister with Lua.
LUA -> UnsetGlobal ( "SONGMAN" );
2004-07-24 06:52:39 +00:00
// Courses depend on Songs and Songs don't depend on Courses.
// So, delete the Courses first.
2004-01-21 03:09:59 +00:00
FreeCourses ();
2004-07-24 06:40:51 +00:00
FreeSongs ();
2003-08-06 05:28:58 +00:00
}
2004-05-25 05:52:57 +00:00
void SongManager :: InitAll ( LoadingWindow * ld )
{
InitSongsFromDisk ( ld );
InitCoursesFromDisk ( ld );
InitAutogenCourses ();
}
2006-01-07 05:43:25 +00:00
static LocalizedString RELOADING ( "SongManager" , "Reloading..." );
2006-05-05 00:41:17 +00:00
void SongManager :: Reload ( bool bAllowFastLoad , LoadingWindow * ld )
2003-08-06 05:28:58 +00:00
{
2006-09-16 03:31:27 +00:00
FILEMAN -> FlushDirCache ( SONGS_DIR );
FILEMAN -> FlushDirCache ( ADDITIONAL_SONGS_DIR );
FILEMAN -> FlushDirCache ( COURSES_DIR );
FILEMAN -> FlushDirCache ( ADDITIONAL_COURSES_DIR );
FILEMAN -> FlushDirCache ( EDIT_SUBDIR );
2003-08-06 05:28:58 +00:00
2004-02-21 01:52:00 +00:00
if ( ld )
2006-01-07 05:43:25 +00:00
ld -> SetText ( RELOADING );
2004-02-21 01:52:00 +00:00
2004-02-16 05:35:06 +00:00
// save scores before unloading songs, of the scores will be lost
PROFILEMAN -> SaveMachineProfile ();
2003-08-06 05:28:58 +00:00
FreeCourses ();
2007-04-01 11:24:40 +00:00
FreeSongs ();
2003-08-06 08:06:27 +00:00
2003-12-31 09:32:21 +00:00
const bool OldVal = PREFSMAN -> m_bFastLoad ;
2006-05-05 00:41:17 +00:00
if ( ! bAllowFastLoad )
PREFSMAN -> m_bFastLoad . Set ( false );
2003-12-30 04:26:39 +00:00
2004-05-25 05:52:57 +00:00
InitAll ( ld );
2004-02-16 05:35:06 +00:00
2006-05-02 22:02:18 +00:00
// reload scores and unlocks afterward
2004-02-16 05:35:06 +00:00
PROFILEMAN -> LoadMachineProfile ();
2006-05-02 22:02:18 +00:00
UNLOCKMAN -> Reload ();
2006-05-16 06:01:12 +00:00
CatalogXml :: Save ( NULL );
2003-12-30 04:26:39 +00:00
2006-05-05 00:41:17 +00:00
if ( ! bAllowFastLoad )
PREFSMAN -> m_bFastLoad . Set ( OldVal );
2006-05-02 08:18:54 +00:00
UpdatePreferredSort ();
2003-07-22 07:47:27 +00:00
}
2002-02-28 19:40:40 +00:00
2003-08-06 05:28:58 +00:00
void SongManager :: InitSongsFromDisk ( LoadingWindow * ld )
2002-05-29 09:47:24 +00:00
{
2003-09-25 02:27:17 +00:00
RageTimer tm ;
2003-07-22 07:47:27 +00:00
LoadStepManiaSongDir ( SONGS_DIR , ld );
2006-06-15 10:54:55 +00:00
const bool bOldVal = PREFSMAN -> m_bFastLoad ;
PREFSMAN -> m_bFastLoad . Set ( PREFSMAN -> m_bFastLoadAdditionalSongs );
2006-04-04 21:52:54 +00:00
LoadStepManiaSongDir ( ADDITIONAL_SONGS_DIR , ld );
2006-06-15 10:54:55 +00:00
PREFSMAN -> m_bFastLoad . Set ( bOldVal );
2003-10-21 20:57:48 +00:00
LOG -> Trace ( "Found %d songs in %f seconds." , ( int ) m_pSongs . size (), tm . GetDeltaTime () );
2002-03-06 08:25:09 +00:00
}
2005-12-20 02:45:25 +00:00
2006-09-17 01:19:19 +00:00
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 \" ." );
2006-01-22 01:00:06 +00:00
void SongManager :: SanityCheckGroupDir ( RString sDir ) const
2002-09-10 07:05:14 +00:00
{
// Check to see if they put a song directly inside the group folder.
2006-01-22 01:00:06 +00:00
vector < RString > arrayFiles ;
2003-02-14 08:15:15 +00:00
GetDirListing ( sDir + "/*.mp3" , arrayFiles );
GetDirListing ( sDir + "/*.ogg" , arrayFiles );
GetDirListing ( sDir + "/*.wav" , arrayFiles );
2002-10-31 02:46:42 +00:00
if ( ! arrayFiles . empty () )
2005-12-20 02:45:25 +00:00
RageException :: Throw ( FOLDER_CONTAINS_MUSIC_FILES . GetValue (), sDir . c_str () );
2002-09-10 07:05:14 +00:00
}
2006-01-22 01:00:06 +00:00
void SongManager :: AddGroup ( RString sDir , RString sGroupDirName )
2002-09-10 07:11:29 +00:00
{
2002-10-31 02:46:42 +00:00
unsigned j ;
2005-06-03 01:57:10 +00:00
for ( j = 0 ; j < m_sSongGroupNames . size (); ++ j )
2006-03-20 01:04:46 +00:00
if ( sGroupDirName == m_sSongGroupNames [ j ] )
break ;
2002-09-10 07:11:29 +00:00
2005-06-03 01:57:10 +00:00
if ( j != m_sSongGroupNames . size () )
2002-09-10 07:11:29 +00:00
return ; /* the group is already added */
// Look for a group banner in this group folder
2006-01-22 01:00:06 +00:00
vector < RString > arrayGroupBanners ;
2003-01-23 04:43:22 +00:00
GetDirListing ( sDir + sGroupDirName + "/*.png" , arrayGroupBanners );
GetDirListing ( sDir + sGroupDirName + "/*.jpg" , arrayGroupBanners );
GetDirListing ( sDir + sGroupDirName + "/*.gif" , arrayGroupBanners );
GetDirListing ( sDir + sGroupDirName + "/*.bmp" , arrayGroupBanners );
2002-09-10 07:11:29 +00:00
2006-01-22 01:00:06 +00:00
RString sBannerPath ;
2002-10-31 02:46:42 +00:00
if ( ! arrayGroupBanners . empty () )
2003-12-10 09:44:16 +00:00
sBannerPath = sDir + sGroupDirName + "/" + arrayGroupBanners [ 0 ] ;
2003-06-17 21:34:43 +00:00
else
{
// Look for a group banner in the parent folder
GetDirListing ( sDir + sGroupDirName + ".png" , arrayGroupBanners );
GetDirListing ( sDir + sGroupDirName + ".jpg" , arrayGroupBanners );
GetDirListing ( sDir + sGroupDirName + ".gif" , arrayGroupBanners );
GetDirListing ( sDir + sGroupDirName + ".bmp" , arrayGroupBanners );
if ( ! arrayGroupBanners . empty () )
sBannerPath = sDir + arrayGroupBanners [ 0 ];
2002-09-10 07:11:29 +00:00
}
2003-12-15 07:53:30 +00:00
LOG -> Trace ( "Group banner for '%s' is '%s'." , sGroupDirName . c_str (),
2004-09-20 17:02:37 +00:00
sBannerPath != "" ? sBannerPath . c_str () : "(none)" );
2005-06-03 01:57:10 +00:00
m_sSongGroupNames . push_back ( sGroupDirName );
m_sSongGroupBannerPaths . push_back ( sBannerPath );
2002-09-10 07:11:29 +00:00
}
2006-01-07 05:43:25 +00:00
static LocalizedString LOADING_SONGS ( "SongManager" , "Loading songs..." );
2006-01-22 01:00:06 +00:00
void SongManager :: LoadStepManiaSongDir ( RString sDir , LoadingWindow * ld )
2002-03-06 08:25:09 +00:00
{
2003-01-23 04:43:22 +00:00
/* Make sure sDir has a trailing slash. */
2003-12-10 09:44:16 +00:00
if ( sDir . Right ( 1 ) != "/" )
sDir += "/" ;
2002-03-06 08:25:09 +00:00
// Find all group directories in "Songs" folder
2006-01-22 01:00:06 +00:00
vector < RString > arrayGroupDirs ;
2003-01-23 04:43:22 +00:00
GetDirListing ( sDir + "*" , arrayGroupDirs , true );
2005-12-20 08:35:47 +00:00
SortRStringArray ( arrayGroupDirs );
2005-06-23 08:05:09 +00:00
StripCvs ( arrayGroupDirs );
2003-09-05 09:14:11 +00:00
2006-01-22 01:00:06 +00:00
FOREACH_CONST ( RString , arrayGroupDirs , s ) // foreach dir in /Songs/
2002-03-06 08:25:09 +00:00
{
2006-01-22 01:00:06 +00:00
RString sGroupDirName = * s ;
2002-03-06 08:25:09 +00:00
2003-01-23 04:43:22 +00:00
SanityCheckGroupDir ( sDir + sGroupDirName );
2002-09-10 07:05:14 +00:00
2002-03-06 08:25:09 +00:00
// Find all Song folders in this group directory
2006-01-22 01:00:06 +00:00
vector < RString > arraySongDirs ;
2003-12-10 09:44:16 +00:00
GetDirListing ( sDir + sGroupDirName + "/*" , arraySongDirs , true , true );
2005-06-23 08:05:09 +00:00
StripCvs ( arraySongDirs );
2005-12-20 08:35:47 +00:00
SortRStringArray ( arraySongDirs );
2002-03-06 08:25:09 +00:00
2004-09-20 17:02:37 +00:00
LOG -> Trace ( "Attempting to load %i songs from \" %s \" " , int ( arraySongDirs . size ()),
( sDir + sGroupDirName ). c_str () );
2002-09-09 02:23:47 +00:00
int loaded = 0 ;
2007-04-07 06:04:40 +00:00
SongPointerVector & index_entry = m_mapSongGroupIndex [ sGroupDirName ];
2007-04-03 23:04:25 +00:00
2004-09-20 17:02:37 +00:00
for ( unsigned j = 0 ; j < arraySongDirs . size (); ++ j ) // for each song dir
2002-03-06 08:25:09 +00:00
{
2006-01-22 01:00:06 +00:00
RString sSongDirName = arraySongDirs [ j ];
2002-03-06 08:25:09 +00:00
// this is a song directory. Load a new song!
2004-09-20 17:02:37 +00:00
if ( ld )
{
2006-01-07 05:43:25 +00:00
ld -> SetText ( LOADING_SONGS . GetValue () + ssprintf ( " \n %s \n %s" ,
2004-09-20 17:02:37 +00:00
Basename ( sGroupDirName ). c_str (),
Basename ( sSongDirName ). c_str ()));
2002-11-17 09:13:35 +00:00
ld -> Paint ();
}
2002-03-06 08:25:09 +00:00
Song * pNewSong = new Song ;
2006-11-12 03:26:25 +00:00
if ( ! pNewSong -> LoadFromSongDir ( sSongDirName ) )
2004-09-20 17:02:37 +00:00
{
2002-09-10 22:55:12 +00:00
/* The song failed to load. */
delete pNewSong ;
continue ;
}
2006-01-27 03:34:15 +00:00
m_pSongs . push_back ( pNewSong );
2007-04-03 23:04:25 +00:00
index_entry . push_back ( pNewSong );
2002-09-09 02:23:47 +00:00
loaded ++ ;
}
2004-09-17 04:07:33 +00:00
LOG -> Trace ( "Loaded %i songs from \" %s \" " , loaded , ( sDir + sGroupDirName ). c_str () );
2002-09-09 02:23:47 +00:00
/* 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. */
2002-09-10 07:11:29 +00:00
AddGroup ( sDir , sGroupDirName );
2003-06-15 01:53:51 +00:00
/* Cache and load the group banner. */
2005-06-23 08:05:09 +00:00
BANNERCACHE -> CacheBanner ( GetSongGroupBannerPath ( sGroupDirName ) );
2003-09-05 09:14:11 +00:00
/* Load the group sym links (if any)*/
LoadGroupSymLinks ( sDir , sGroupDirName );
}
}
2005-06-04 21:22:50 +00:00
// Instead of "symlinks", songs should have membership in multiple groups.
2005-05-26 09:35:57 +00:00
// -Chris
2006-01-22 01:00:06 +00:00
void SongManager :: LoadGroupSymLinks ( RString sDir , RString sGroupFolder )
2003-09-05 09:14:11 +00:00
{
// Find all symlink files in this folder
2006-01-22 01:00:06 +00:00
vector < RString > arraySymLinks ;
2003-12-10 09:44:16 +00:00
GetDirListing ( sDir + sGroupFolder + "/*.include" , arraySymLinks , false );
2005-12-20 08:35:47 +00:00
SortRStringArray ( arraySymLinks );
2007-04-07 06:04:40 +00:00
SongPointerVector & index_entry = m_mapSongGroupIndex [ sGroupFolder ];
2003-09-05 09:14:11 +00:00
for ( unsigned s = 0 ; s < arraySymLinks . size (); s ++ ) // for each symlink in this dir, add it in as a song.
{
MsdFile msdF ;
2006-12-01 22:15:26 +00:00
msdF . ReadFile ( sDir + sGroupFolder + "/" + arraySymLinks [ s ]. c_str (), false ); // don't unescape
2006-01-22 01:00:06 +00:00
RString sSymDestination = msdF . GetParam ( 0 , 1 ); // Should only be 1 vale¶m...period.
2003-09-05 09:14:11 +00:00
Song * pNewSong = new Song ;
2006-11-12 03:26:25 +00:00
if ( ! pNewSong -> LoadFromSongDir ( sSymDestination ) )
2005-05-26 09:35:57 +00:00
{
2003-09-05 09:14:11 +00:00
delete pNewSong ; // The song failed to load.
2005-05-26 09:35:57 +00:00
}
2003-09-05 09:14:11 +00:00
else
{
2004-06-05 05:13:23 +00:00
const vector < Steps *>& vpSteps = pNewSong -> GetAllSteps ();
while ( vpSteps . size () )
2005-07-29 02:23:02 +00:00
pNewSong -> DeleteSteps ( vpSteps [ 0 ] );
2004-06-05 05:13:23 +00:00
2005-06-04 21:22:50 +00:00
FOREACH_BackgroundLayer ( i )
pNewSong -> GetBackgroundChanges ( i ). clear ();
2003-09-05 09:14:11 +00:00
pNewSong -> m_bIsSymLink = true ; // Very important so we don't double-parse later
pNewSong -> m_sGroupName = sGroupFolder ;
m_pSongs . push_back ( pNewSong );
2007-04-03 23:04:25 +00:00
index_entry . push_back ( pNewSong );
2003-09-05 09:14:11 +00:00
}
2002-03-06 08:25:09 +00:00
}
}
2003-11-25 22:56:48 +00:00
void SongManager :: PreloadSongImages ()
{
2006-10-07 07:43:18 +00:00
if ( PREFSMAN -> m_BannerCache != BNCACHE_FULL )
2003-11-25 22:56:48 +00:00
return ;
2005-06-30 22:04:10 +00:00
/* Load textures before unloading old ones, so we don't reload textures
* that we don't need to. */
RageTexturePreloader preload ;
2007-05-05 11:12:34 +00:00
const vector < Song *> & songs = GetSongs ();
2004-09-21 07:53:39 +00:00
for ( unsigned i = 0 ; i < songs . size (); ++ i )
2003-11-25 22:56:48 +00:00
{
if ( ! songs [ i ] -> HasBanner () )
continue ;
2004-03-26 07:56:18 +00:00
const RageTextureID ID = Sprite :: SongBannerTexture ( songs [ i ] -> GetBannerPath () );
2005-06-30 22:04:10 +00:00
preload . Load ( ID );
2003-11-25 22:56:48 +00:00
}
2003-11-25 23:01:53 +00:00
vector < Course *> courses ;
2007-05-05 11:12:34 +00:00
GetAllCourses ( courses , false );
2004-09-21 07:53:39 +00:00
for ( unsigned i = 0 ; i < courses . size (); ++ i )
2003-11-25 23:01:53 +00:00
{
if ( ! courses [ i ] -> HasBanner () )
continue ;
2007-05-25 22:26:43 +00:00
const RageTextureID ID = Sprite :: SongBannerTexture ( courses [ i ] -> GetBannerPath () );
2005-06-30 22:04:10 +00:00
preload . Load ( ID );
2003-11-25 23:01:53 +00:00
}
2005-06-30 22:04:10 +00:00
preload . Swap ( m_TexturePreload );
2003-11-25 22:56:48 +00:00
}
2003-08-06 05:28:58 +00:00
void SongManager :: FreeSongs ()
2002-02-28 19:40:40 +00:00
{
2005-06-03 01:57:10 +00:00
m_sSongGroupNames . clear ();
m_sSongGroupBannerPaths . clear ();
2004-05-29 20:15:07 +00:00
2002-10-31 02:46:42 +00:00
for ( unsigned i = 0 ; i < m_pSongs . size (); i ++ )
2002-02-28 19:40:40 +00:00
SAFE_DELETE ( m_pSongs [ i ] );
2002-10-24 20:15:24 +00:00
m_pSongs . clear ();
2007-04-03 23:04:25 +00:00
m_mapSongGroupIndex . clear ();
2002-06-14 22:25:22 +00:00
2005-06-03 01:57:10 +00:00
m_sSongGroupBannerPaths . clear ();
2004-05-29 20:15:07 +00:00
2007-04-10 22:41:45 +00:00
m_pPopularSongs . clear ();
2004-05-29 20:15:07 +00:00
m_pShuffledSongs . clear ();
2002-02-28 19:40:40 +00:00
}
2007-04-07 06:13:00 +00:00
RString SongManager :: GetSongGroupBannerPath ( RString sSongGroup ) const
2002-03-06 08:25:09 +00:00
{
2005-06-23 08:05:09 +00:00
for ( unsigned i = 0 ; i < m_sSongGroupNames . size (); ++ i )
{
if ( sSongGroup == m_sSongGroupNames [ i ] )
return m_sSongGroupBannerPaths [ i ];
}
2002-03-06 08:25:09 +00:00
2006-01-22 01:00:06 +00:00
return RString ();
2002-03-06 08:25:09 +00:00
}
2002-04-01 02:04:43 +00:00
2007-04-07 06:13:00 +00:00
void SongManager :: GetSongGroupNames ( vector < RString > & AddTo ) const
2002-04-01 02:04:43 +00:00
{
2005-06-03 01:57:10 +00:00
AddTo . insert ( AddTo . end (), m_sSongGroupNames . begin (), m_sSongGroupNames . end () );
2002-04-16 17:31:00 +00:00
}
2002-04-01 02:04:43 +00:00
2007-04-07 06:13:00 +00:00
bool SongManager :: DoesSongGroupExist ( RString sSongGroup ) const
2003-02-16 23:54:30 +00:00
{
2005-06-23 08:05:09 +00:00
return find ( m_sSongGroupNames . begin (), m_sSongGroupNames . end (), sSongGroup ) != m_sSongGroupNames . end ();
2003-02-16 23:54:30 +00:00
}
2007-04-07 06:13:00 +00:00
RageColor SongManager :: GetSongGroupColor ( const RString & sSongGroup ) const
2002-04-16 17:31:00 +00:00
{
2005-06-23 08:05:09 +00:00
for ( unsigned i = 0 ; i < m_sSongGroupNames . size (); i ++ )
2002-04-01 02:04:43 +00:00
{
2005-06-23 08:05:09 +00:00
if ( m_sSongGroupNames [ i ] == sSongGroup )
2006-03-19 05:59:24 +00:00
return SONG_GROUP_COLOR . GetValue ( i % NUM_SONG_GROUP_COLORS );
2002-04-01 02:04:43 +00:00
}
2005-06-23 08:05:09 +00:00
ASSERT_M ( 0 , ssprintf ( "requested color for song group '%s' that doesn't exist" , sSongGroup . c_str ()) );
return RageColor ( 1 , 1 , 1 , 1 );
2002-04-01 02:04:43 +00:00
}
2007-04-07 06:13:00 +00:00
RageColor SongManager :: GetSongColor ( const Song * pSong ) const
2002-08-27 23:31:41 +00:00
{
ASSERT ( pSong );
2003-04-20 01:40:40 +00:00
2006-05-01 12:45:07 +00:00
// Use unlock color if applicable
const UnlockEntry * pUE = UNLOCKMAN -> FindSong ( pSong );
2006-05-03 21:33:19 +00:00
if ( pUE && USE_UNLOCK_COLOR . GetValue () )
2006-05-02 01:18:21 +00:00
return UNLOCK_COLOR . GetValue ();
2006-05-01 12:45:07 +00:00
2006-03-20 01:04:46 +00:00
if ( USE_PREFERRED_SORT_COLOR )
2002-08-27 23:31:41 +00:00
{
2006-05-01 21:49:59 +00:00
FOREACH_CONST ( SongPointerVector , m_vPreferredSongSort , v )
2004-02-29 07:36:03 +00:00
{
2006-03-20 01:04:46 +00:00
FOREACH_CONST ( Song * , * v , s )
{
if ( * s == pSong )
{
2006-05-01 21:49:59 +00:00
int i = v - m_vPreferredSongSort . begin ();
2006-03-20 01:04:46 +00:00
return SONG_GROUP_COLOR . GetValue ( i % NUM_SONG_GROUP_COLORS );
}
}
2004-02-29 07:36:03 +00:00
}
2003-04-20 01:40:40 +00:00
2006-05-01 21:49:59 +00:00
int i = m_vPreferredSongSort . size ();
2006-03-20 01:04:46 +00:00
return SONG_GROUP_COLOR . GetValue ( i % NUM_SONG_GROUP_COLORS );
2002-08-27 23:31:41 +00:00
}
2006-03-20 01:04:46 +00:00
else
{
2002-08-27 23:31:41 +00:00
2006-03-20 01:04:46 +00:00
/* 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;
const vector < Steps *>& vpSteps = pSong -> GetAllSteps ();
for ( unsigned i = 0 ; i < vpSteps . size (); i ++ )
{
const Steps * pSteps = vpSteps [ i ];
switch ( pSteps -> GetDifficulty () )
{
2007-02-22 07:18:05 +00:00
case Difficulty_Challenge :
case Difficulty_Edit :
2006-03-20 01:04:46 +00:00
continue ;
}
// if(pSteps->m_StepsType != st)
// continue;
if ( pSteps -> GetMeter () >= EXTRA_COLOR_METER )
return ( RageColor ) EXTRA_COLOR ;
}
return GetSongGroupColor ( pSong -> m_sGroupName );
}
2005-06-03 01:57:10 +00:00
}
2007-04-07 06:13:00 +00:00
RString SongManager :: GetCourseGroupBannerPath ( const RString & sCourseGroup ) const
2005-06-03 01:57:10 +00:00
{
2006-01-22 01:00:06 +00:00
map < RString , CourseGroupInfo >:: const_iterator iter = m_mapCourseGroupToInfo . find ( sCourseGroup );
2005-07-31 05:41:32 +00:00
if ( iter == m_mapCourseGroupToInfo . end () )
2005-06-03 01:57:10 +00:00
{
2005-07-31 05:41:32 +00:00
ASSERT_M ( 0 , ssprintf ( "requested banner for course group '%s' that doesn't exist" , sCourseGroup . c_str ()) );
2006-01-22 01:00:06 +00:00
return RString ();
2005-07-31 05:41:32 +00:00
}
else
{
return iter -> second . m_sBannerPath ;
2005-06-03 01:57:10 +00:00
}
2005-06-23 08:05:09 +00:00
}
2007-04-07 06:13:00 +00:00
void SongManager :: GetCourseGroupNames ( vector < RString > & AddTo ) const
2005-06-23 08:05:09 +00:00
{
2006-01-22 01:00:06 +00:00
FOREACHM_CONST ( RString , CourseGroupInfo , m_mapCourseGroupToInfo , iter )
2005-07-31 05:41:32 +00:00
AddTo . push_back ( iter -> first );
2005-06-23 08:05:09 +00:00
}
2007-04-07 06:13:00 +00:00
bool SongManager :: DoesCourseGroupExist ( const RString & sCourseGroup ) const
2005-06-23 08:05:09 +00:00
{
2005-07-31 05:41:32 +00:00
return m_mapCourseGroupToInfo . find ( sCourseGroup ) != m_mapCourseGroupToInfo . end ();
2005-06-23 08:05:09 +00:00
}
2007-04-07 06:13:00 +00:00
RageColor SongManager :: GetCourseGroupColor ( const RString & sCourseGroup ) const
2005-06-23 08:05:09 +00:00
{
2005-07-31 05:41:32 +00:00
int iIndex = 0 ;
2006-01-22 01:00:06 +00:00
FOREACHM_CONST ( RString , CourseGroupInfo , m_mapCourseGroupToInfo , iter )
2005-06-23 08:05:09 +00:00
{
2005-07-31 05:41:32 +00:00
if ( iter -> first == sCourseGroup )
return SONG_GROUP_COLOR . GetValue ( iIndex % NUM_SONG_GROUP_COLORS );
iIndex ++ ;
2005-06-23 08:05:09 +00:00
}
ASSERT_M ( 0 , ssprintf ( "requested color for course group '%s' that doesn't exist" , sCourseGroup . c_str ()) );
return RageColor ( 1 , 1 , 1 , 1 );
2005-06-03 01:57:10 +00:00
}
2007-04-07 06:13:00 +00:00
RageColor SongManager :: GetCourseColor ( const Course * pCourse ) const
2005-06-03 01:57:10 +00:00
{
2006-05-01 21:49:59 +00:00
// Use unlock color if applicable
const UnlockEntry * pUE = UNLOCKMAN -> FindCourse ( pCourse );
2006-05-03 21:33:19 +00:00
if ( pUE && USE_UNLOCK_COLOR . GetValue () )
2006-05-02 01:18:21 +00:00
return UNLOCK_COLOR . GetValue ();
2006-05-01 21:49:59 +00:00
if ( USE_PREFERRED_SORT_COLOR )
{
FOREACH_CONST ( CoursePointerVector , m_vPreferredCourseSort , v )
{
FOREACH_CONST ( Course * , * v , s )
{
if ( * s == pCourse )
{
int i = v - m_vPreferredCourseSort . begin ();
return COURSE_GROUP_COLOR . GetValue ( i % NUM_COURSE_GROUP_COLORS );
}
}
}
int i = m_vPreferredCourseSort . size ();
return COURSE_GROUP_COLOR . GetValue ( i % NUM_COURSE_GROUP_COLORS );
}
else
{
return GetCourseGroupColor ( pCourse -> m_sGroupName );
}
2002-08-27 23:31:41 +00:00
}
2007-04-07 05:10:29 +00:00
const vector < Song *> & SongManager :: GetSongs ( const RString & sGroupName ) const
2004-03-07 00:17:50 +00:00
{
2007-04-07 02:34:17 +00:00
static const vector < Song *> vEmpty ;
if ( sGroupName == GROUP_ALL )
return m_pSongs ;
2007-04-07 06:04:40 +00:00
map < RString , SongPointerVector , Comp >:: const_iterator iter = m_mapSongGroupIndex . find ( sGroupName );
2007-04-03 23:04:25 +00:00
if ( iter != m_mapSongGroupIndex . end () )
return iter -> second ;
2007-04-07 02:34:17 +00:00
return vEmpty ;
2004-03-07 00:17:50 +00:00
}
2007-04-10 22:41:45 +00:00
void SongManager :: GetPopularSongs ( vector < Song *> & AddTo , const RString & sGroupName ) const
2004-03-07 00:17:50 +00:00
{
2007-04-07 02:34:17 +00:00
if ( sGroupName == GROUP_ALL )
{
2007-04-10 22:41:45 +00:00
AddTo . insert ( AddTo . end (), m_pPopularSongs . begin (), m_pPopularSongs . end () );
2007-04-07 02:34:17 +00:00
return ;
}
2007-04-10 22:41:45 +00:00
FOREACH_CONST ( Song * , m_pPopularSongs , song )
2007-04-07 02:34:17 +00:00
{
if ( ( * song ) -> m_sGroupName == sGroupName )
AddTo . push_back ( * song );
}
2006-03-20 01:04:46 +00:00
}
2007-02-23 21:13:45 +00:00
void SongManager :: GetPreferredSortSongs ( vector < Song *> & AddTo ) const
2006-03-20 01:04:46 +00:00
{
2006-05-01 21:49:59 +00:00
if ( m_vPreferredSongSort . empty () )
2006-03-20 01:04:46 +00:00
{
2007-04-07 05:10:29 +00:00
AddTo . insert ( AddTo . end (), m_pSongs . begin (), m_pSongs . end () );
2006-03-20 01:04:46 +00:00
return ;
}
2006-05-01 21:49:59 +00:00
FOREACH_CONST ( SongPointerVector , m_vPreferredSongSort , v )
2007-04-24 02:13:23 +00:00
AddTo . insert ( AddTo . end (), v -> begin (), v -> end () );
2002-04-01 02:04:43 +00:00
}
2002-04-16 17:31:00 +00:00
2006-05-01 21:49:59 +00:00
void SongManager :: GetPreferredSortCourses ( CourseType ct , vector < Course *> & AddTo , bool bIncludeAutogen ) const
{
if ( m_vPreferredCourseSort . empty () )
{
GetCourses ( ct , AddTo , bIncludeAutogen );
return ;
}
FOREACH_CONST ( CoursePointerVector , m_vPreferredCourseSort , v )
2006-05-02 08:18:54 +00:00
{
2006-05-01 21:49:59 +00:00
FOREACH_CONST ( Course * , * v , c )
2006-05-02 08:18:54 +00:00
{
Course * pCourse = * c ;
if ( pCourse -> GetCourseType () == ct )
AddTo . push_back ( pCourse );
}
}
2006-05-01 21:49:59 +00:00
}
2003-02-05 18:34:27 +00:00
int SongManager :: GetNumSongs () const
2003-02-03 05:53:59 +00:00
{
return m_pSongs . size ();
}
2006-05-03 21:33:19 +00:00
int SongManager :: GetNumUnlockedSongs () const
{
int num = 0 ;
FOREACH_CONST ( Song * , m_pSongs , i )
{
2006-05-16 00:47:02 +00:00
if ( UNLOCKMAN -> SongIsLocked ( * i ) )
continue ;
num ++ ;
}
return num ;
}
int SongManager :: GetNumSelectableAndUnlockedSongs () const
{
2007-08-13 19:20:40 +00:00
int iNum = 0 ;
2006-05-16 00:47:02 +00:00
FOREACH_CONST ( Song * , m_pSongs , i )
{
if ( UNLOCKMAN -> SongIsLocked ( * i ) )
continue ;
2007-08-13 19:18:55 +00:00
if ( ( * i ) -> GetDisplayed () != Song :: SHOW_ALWAYS )
2006-05-16 00:47:02 +00:00
continue ;
2007-08-13 19:20:40 +00:00
iNum ++ ;
2006-05-03 21:33:19 +00:00
}
2007-08-13 19:20:40 +00:00
return iNum ;
2006-05-03 21:33:19 +00:00
}
int SongManager :: GetNumAdditionalSongs () const
{
2007-08-13 19:20:40 +00:00
int iNum = 0 ;
2006-05-03 21:33:19 +00:00
FOREACH_CONST ( Song * , m_pSongs , i )
{
2007-05-05 11:12:34 +00:00
if ( WasLoadedFromAdditionalSongs ( * i ) )
2007-08-13 19:20:40 +00:00
iNum ++ ;
2006-05-03 21:33:19 +00:00
}
2007-08-13 19:20:40 +00:00
return iNum ;
2006-05-03 21:33:19 +00:00
}
2005-06-23 08:05:09 +00:00
int SongManager :: GetNumSongGroups () const
2003-02-03 05:53:59 +00:00
{
2005-06-03 01:57:10 +00:00
return m_sSongGroupNames . size ();
2003-02-03 05:53:59 +00:00
}
2003-02-14 21:42:44 +00:00
int SongManager :: GetNumCourses () const
{
return m_pCourses . size ();
}
2006-08-14 20:05:35 +00:00
int SongManager :: GetNumAdditionalCourses () const
{
int num = 0 ;
FOREACH_CONST ( Course * , m_pCourses , i )
{
2007-05-05 11:12:34 +00:00
if ( WasLoadedFromAdditionalCourses ( * i ) )
2006-08-14 20:05:35 +00:00
num ++ ;
}
return num ;
}
2005-06-23 08:05:09 +00:00
int SongManager :: GetNumCourseGroups () const
{
2005-07-31 05:41:32 +00:00
return m_mapCourseGroupToInfo . size ();
2005-06-23 08:05:09 +00:00
}
2005-08-05 10:07:49 +00:00
int SongManager :: GetNumEditCourses ( ProfileSlot slot ) const
{
int iNum = 0 ;
FOREACH_CONST ( Course * , m_pCourses , p )
{
if ( ( * p ) -> GetLoadedFromProfileSlot () == slot )
iNum ++ ;
}
return iNum ;
}
2006-01-22 01:00:06 +00:00
RString SongManager :: ShortenGroupName ( RString sLongGroupName )
2002-04-16 17:31:00 +00:00
{
2005-10-06 07:01:58 +00:00
static TitleSubst tsub ( "Groups" );
TitleFields title ;
title . Title = sLongGroupName ;
tsub . Subst ( title );
return title . Title ;
2003-02-10 05:30:12 +00:00
}
2006-01-07 05:43:25 +00:00
static LocalizedString LOADING_COURSES ( "SongManager" , "Loading courses..." );
2003-08-06 05:28:58 +00:00
void SongManager :: InitCoursesFromDisk ( LoadingWindow * ld )
2002-06-14 22:25:22 +00:00
{
2003-08-16 23:34:47 +00:00
LOG -> Trace ( "Loading courses." );
2006-08-14 20:05:35 +00:00
vector < RString > vsCourseDirs ;
vsCourseDirs . push_back ( COURSES_DIR );
vsCourseDirs . push_back ( ADDITIONAL_COURSES_DIR );
2006-10-15 07:16:57 +00:00
vector < RString > vsCourseGroupNames ;
2006-08-14 20:05:35 +00:00
FOREACH_CONST ( RString , vsCourseDirs , sDir )
2003-04-11 00:29:44 +00:00
{
2006-08-14 20:05:35 +00:00
// Find all group directories in Courses dir
2006-10-15 07:15:40 +00:00
GetDirListing ( * sDir + "*" , vsCourseGroupNames , true , true );
2005-07-31 05:41:32 +00:00
StripCvs ( vsCourseGroupNames );
2006-10-15 07:16:57 +00:00
}
2006-10-15 07:30:58 +00:00
// Search for courses both in COURSES_DIR and in subdirectories
vsCourseGroupNames . push_back ( COURSES_DIR );
2006-10-15 07:16:57 +00:00
SortRStringArray ( vsCourseGroupNames );
FOREACH_CONST ( RString , vsCourseGroupNames , sCourseGroup ) // for each dir in /Courses/
{
// Find all CRS files in this group directory
vector < RString > vsCoursePaths ;
GetDirListing ( * sCourseGroup + "/*.crs" , vsCoursePaths , false , true );
SortRStringArray ( vsCoursePaths );
FOREACH_CONST ( RString , vsCoursePaths , sCoursePath )
2003-04-11 00:29:44 +00:00
{
2006-10-15 07:16:57 +00:00
if ( ld )
2005-06-03 01:57:10 +00:00
{
2006-10-15 07:16:57 +00:00
ld -> SetText ( LOADING_COURSES . GetValue () + ssprintf ( " \n %s \n %s" ,
Basename ( * sCourseGroup ). c_str (),
Basename ( * sCoursePath ). c_str ()));
ld -> Paint ();
2005-06-03 01:57:10 +00:00
}
2006-10-15 07:16:57 +00:00
Course * pCourse = new Course ;
CourseLoaderCRS :: LoadFromCRSFile ( * sCoursePath , * pCourse );
m_pCourses . push_back ( pCourse );
2003-04-11 00:29:44 +00:00
}
}
2005-07-31 05:41:32 +00:00
RefreshCourseGroupInfo ();
2003-03-27 01:56:21 +00:00
}
2002-07-31 04:06:34 +00:00
2003-03-27 01:56:21 +00:00
void SongManager :: InitAutogenCourses ()
{
2002-07-02 17:34:20 +00:00
//
2003-01-21 05:14:59 +00:00
// Create group courses for Endless and Nonstop
2002-07-02 17:34:20 +00:00
//
2006-01-22 01:00:06 +00:00
vector < RString > saGroupNames ;
2005-06-23 08:05:09 +00:00
this -> GetSongGroupNames ( saGroupNames );
2004-05-20 23:14:36 +00:00
Course * pCourse ;
for ( unsigned g = 0 ; g < saGroupNames . size (); g ++ ) // foreach Group
2002-06-14 22:25:22 +00:00
{
2006-01-22 01:00:06 +00:00
RString sGroupName = saGroupNames [ g ];
2002-06-14 22:25:22 +00:00
2004-05-20 23:14:36 +00:00
// Generate random courses from each group.
2003-03-27 01:56:21 +00:00
pCourse = new Course ;
2007-02-22 07:18:05 +00:00
CourseUtil :: AutogenEndlessFromGroup ( sGroupName , Difficulty_Medium , * pCourse );
2003-03-27 01:56:21 +00:00
m_pCourses . push_back ( pCourse );
pCourse = new Course ;
2007-02-22 07:18:05 +00:00
CourseUtil :: AutogenNonstopFromGroup ( sGroupName , Difficulty_Medium , * pCourse );
2003-03-27 01:56:21 +00:00
m_pCourses . push_back ( pCourse );
2002-06-14 22:25:22 +00:00
}
2004-05-20 03:26:36 +00:00
2007-04-07 05:36:40 +00:00
vector < Song *> apCourseSongs = GetSongs ();
2004-05-20 03:26:36 +00:00
2004-05-20 23:14:36 +00:00
// Generate "All Songs" endless course.
2004-05-20 03:26:36 +00:00
pCourse = new Course ;
2007-02-22 07:18:05 +00:00
CourseUtil :: AutogenEndlessFromGroup ( "" , Difficulty_Medium , * pCourse );
2004-05-20 03:26:36 +00:00
m_pCourses . push_back ( pCourse );
2004-05-25 02:17:17 +00:00
/* Generate Oni courses from artists. Only create courses if we have at least
* four songs from an artist; create 3- and 4-song courses. */
{
2004-05-25 04:08:21 +00:00
/* 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. */
2007-04-07 05:36:40 +00:00
vector < Song *> apSongs = this -> GetSongs ();
2004-05-25 04:08:21 +00:00
SongUtil :: SortSongPointerArrayByDisplayArtist ( apSongs );
2004-05-25 02:17:17 +00:00
2006-01-22 01:00:06 +00:00
RString sCurArtist = "" ;
RString sCurArtistTranslit = "" ;
2004-05-25 02:17:17 +00:00
int iCurArtistCount = 0 ;
2004-05-25 04:08:21 +00:00
vector < Song *> aSongs ;
2004-05-25 02:17:17 +00:00
unsigned i = 0 ;
do {
2006-01-22 01:00:06 +00:00
RString sArtist = i >= apSongs . size () ? RString ( "" ) : apSongs [ i ] -> GetDisplayArtist ();
RString sTranslitArtist = i >= apSongs . size () ? RString ( "" ) : apSongs [ i ] -> GetTranslitArtist ();
2004-05-25 02:17:17 +00:00
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". */
2004-05-25 04:08:21 +00:00
if ( iCurArtistCount >= 3 && sCurArtistTranslit != "" &&
2004-08-25 08:25:43 +00:00
sCurArtistTranslit . CompareNoCase ( "Unknown artist" ) &&
sCurArtist . CompareNoCase ( "Unknown artist" ) )
2004-05-25 02:17:17 +00:00
{
pCourse = new Course ;
2007-02-22 07:18:05 +00:00
CourseUtil :: AutogenOniFromArtist ( sCurArtist , sCurArtistTranslit , aSongs , Difficulty_Hard , * pCourse );
2004-05-25 02:17:17 +00:00
m_pCourses . push_back ( pCourse );
}
aSongs . clear ();
if ( i < apSongs . size () )
{
sCurArtist = sArtist ;
2004-05-25 04:08:21 +00:00
sCurArtistTranslit = sTranslitArtist ;
2004-05-25 02:17:17 +00:00
iCurArtistCount = 1 ;
aSongs . push_back ( apSongs [ i ] );
}
} while ( i ++ < apSongs . size () );
}
2002-06-14 22:25:22 +00:00
}
2003-01-22 05:29:27 +00:00
void SongManager :: FreeCourses ()
2002-06-14 22:25:22 +00:00
{
2003-01-22 05:29:27 +00:00
for ( unsigned i = 0 ; i < m_pCourses . size (); i ++ )
delete m_pCourses [ i ];
m_pCourses . clear ();
2004-05-29 20:15:07 +00:00
2007-04-10 22:41:45 +00:00
FOREACH_CourseType ( ct )
m_pPopularCourses [ ct ]. clear ();
2004-05-29 20:15:07 +00:00
m_pShuffledCourses . clear ();
2005-06-03 01:57:10 +00:00
2005-07-31 05:41:32 +00:00
m_mapCourseGroupToInfo . clear ();
2002-06-14 22:25:22 +00:00
}
2002-08-01 13:42:56 +00:00
2006-11-28 06:56:33 +00:00
void SongManager :: DeleteAutogenCourses ()
{
vector < Course *> vNewCourses ;
for ( vector < Course *>:: iterator it = m_pCourses . begin (); it != m_pCourses . end (); ++ it )
{
if ( ( * it ) -> m_bIsAutogen )
{
delete * it ;
}
else
{
vNewCourses . push_back ( * it );
}
}
m_pCourses . swap ( vNewCourses );
UpdatePopular ();
UpdateShuffled ();
RefreshCourseGroupInfo ();
}
2005-07-29 02:23:02 +00:00
void SongManager :: AddCourse ( Course * pCourse )
{
m_pCourses . push_back ( pCourse );
2006-03-20 01:04:46 +00:00
UpdatePopular ();
2005-07-29 02:23:02 +00:00
UpdateShuffled ();
2005-07-31 05:41:32 +00:00
m_mapCourseGroupToInfo [ pCourse -> m_sGroupName ]; // insert
2005-07-29 02:23:02 +00:00
}
void SongManager :: DeleteCourse ( Course * pCourse )
{
vector < Course *>:: iterator iter = find ( m_pCourses . begin (), m_pCourses . end (), pCourse );
ASSERT ( iter != m_pCourses . end () );
m_pCourses . erase ( iter );
2006-03-20 01:04:46 +00:00
UpdatePopular ();
2005-07-29 02:23:02 +00:00
UpdateShuffled ();
2005-07-31 05:41:32 +00:00
RefreshCourseGroupInfo ();
2005-07-29 02:23:02 +00:00
}
2006-06-12 10:49:44 +00:00
void SongManager :: InvalidateCachedTrails ()
{
FOREACH_CONST ( Course * , m_pCourses , pCourse )
{
const Course & c = ** pCourse ;
if ( c . IsAnEdit () )
c . m_TrailCache . clear ();
}
}
2002-12-21 18:23:37 +00:00
/* Called periodically to wipe out cached NoteData. This is called when we change
* screens. */
2003-12-21 02:54:23 +00:00
void SongManager :: Cleanup ()
2002-12-21 18:23:37 +00:00
{
2004-06-03 08:22:02 +00:00
for ( unsigned i = 0 ; i < m_pSongs . size (); i ++ )
2002-12-21 18:23:37 +00:00
{
2003-05-05 04:13:39 +00:00
Song * pSong = m_pSongs [ i ];
2004-06-04 23:29:13 +00:00
const vector < Steps *>& vpSteps = pSong -> GetAllSteps ();
for ( unsigned n = 0 ; n < vpSteps . size (); n ++ )
2003-05-05 04:13:39 +00:00
{
2004-06-04 23:29:13 +00:00
Steps * pSteps = vpSteps [ n ];
2004-05-24 03:41:39 +00:00
pSteps -> Compress ();
2002-12-21 18:23:37 +00:00
}
}
2004-06-11 21:31:34 +00:00
}
2003-12-21 02:54:23 +00:00
2006-11-13 17:07:42 +00:00
/* 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
2006-11-28 06:56:33 +00:00
* pointers. Currently, the only time Steps are altered independently of the
2006-11-13 17:07:42 +00:00
* Courses and Songs is in Edit Mode, which updates the other pointers it needs. */
2006-11-13 18:28:29 +00:00
void SongManager :: Invalidate ( const Song * pStaleSong )
2006-11-12 03:26:25 +00:00
{
2006-11-28 06:56:33 +00:00
// TODO: This is unnecessarily expensive.
// Can we regenerate only the autogen courses that are affected?
DeleteAutogenCourses ();
2006-11-13 17:07:42 +00:00
FOREACH ( Course * , this -> m_pCourses , pCourse )
2006-11-12 03:26:25 +00:00
{
2006-11-13 17:07:42 +00:00
( * pCourse ) -> Invalidate ( pStaleSong );
2006-11-12 03:26:25 +00:00
}
2006-11-28 06:56:33 +00:00
InitAutogenCourses ();
2006-11-13 17:07:42 +00:00
UpdatePopular ();
UpdateShuffled ();
RefreshCourseGroupInfo ();
2006-11-12 03:26:25 +00:00
}
2004-07-24 06:40:51 +00:00
void SongManager :: RegenerateNonFixedCourses ()
{
for ( unsigned i = 0 ; i < m_pCourses . size (); i ++ )
m_pCourses [ i ] -> RegenerateNonFixedTrails ();
2002-12-21 18:23:37 +00:00
}
2004-06-05 08:08:42 +00:00
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 ();
}
}
2007-04-07 06:13:00 +00:00
void SongManager :: GetStepsLoadedFromProfile ( vector < Steps *> & AddTo , ProfileSlot slot ) const
2005-11-30 22:42:28 +00:00
{
2007-05-05 11:12:34 +00:00
const vector < Song *> & vSongs = GetSongs ();
2005-11-30 22:42:28 +00:00
FOREACH_CONST ( Song * , vSongs , song )
{
( * song ) -> GetStepsLoadedFromProfile ( slot , AddTo );
}
}
2007-04-07 06:13:00 +00:00
Song * SongManager :: GetSongFromSteps ( Steps * pSteps ) const
2005-11-30 22:42:28 +00:00
{
2005-12-06 04:20:20 +00:00
ASSERT ( pSteps );
2007-05-05 11:12:34 +00:00
const vector < Song *> & vSongs = GetSongs ();
2005-11-30 22:42:28 +00:00
FOREACH_CONST ( Song * , vSongs , song )
{
vector < Steps *> vSteps ;
2006-06-13 01:10:37 +00:00
SongUtil :: GetSteps ( * song , vSteps );
2005-11-30 22:42:28 +00:00
FOREACH_CONST ( Steps * , vSteps , steps )
{
if ( * steps == pSteps )
{
return * song ;
}
}
}
ASSERT ( 0 );
return NULL ;
}
void SongManager :: DeleteSteps ( Steps * pSteps )
{
Song * pSong = GetSongFromSteps ( pSteps );
pSong -> DeleteSteps ( pSteps );
}
2006-04-04 21:52:54 +00:00
bool SongManager :: WasLoadedFromAdditionalSongs ( const Song * pSong ) const
{
RString sDir = pSong -> GetSongDir ();
return BeginsWith ( sDir , ADDITIONAL_SONGS_DIR );
}
2006-08-14 20:05:35 +00:00
bool SongManager :: WasLoadedFromAdditionalCourses ( const Course * pCourse ) const
{
RString sDir = pCourse -> m_sPath ;
return BeginsWith ( sDir , ADDITIONAL_COURSES_DIR );
}
2007-04-07 06:13:00 +00:00
void SongManager :: GetAllCourses ( vector < Course *> & AddTo , bool bIncludeAutogen ) const
2003-07-20 00:30:24 +00:00
{
for ( unsigned i = 0 ; i < m_pCourses . size (); i ++ )
if ( bIncludeAutogen || ! m_pCourses [ i ] -> m_bIsAutogen )
AddTo . push_back ( m_pCourses [ i ] );
}
2006-05-01 21:49:59 +00:00
void SongManager :: GetCourses ( CourseType ct , vector < Course *> & AddTo , bool bIncludeAutogen ) const
2003-01-21 05:14:59 +00:00
{
2003-01-22 05:29:27 +00:00
for ( unsigned i = 0 ; i < m_pCourses . size (); i ++ )
2005-04-07 08:46:40 +00:00
if ( m_pCourses [ i ] -> GetCourseType () == ct )
2003-03-27 01:56:21 +00:00
if ( bIncludeAutogen || ! m_pCourses [ i ] -> m_bIsAutogen )
AddTo . push_back ( m_pCourses [ i ] );
2003-01-21 05:14:59 +00:00
}
2007-04-07 06:13:00 +00:00
void SongManager :: GetCoursesInGroup ( vector < Course *> & AddTo , const RString & sCourseGroup , bool bIncludeAutogen ) const
2005-06-23 22:43:48 +00: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 ] );
}
2007-04-10 18:24:06 +00:00
bool SongManager :: GetExtraStageInfoFromCourse ( bool bExtra2 , RString sPreferredGroup , Song *& pSongOut , Steps *& pStepsOut )
2002-08-30 20:49:56 +00:00
{
2006-01-22 01:00:06 +00:00
const RString sCourseSuffix = sPreferredGroup + "/" + ( bExtra2 ? "extra2" : "extra1" ) + ".crs" ;
RString sCoursePath = SONGS_DIR + sCourseSuffix ;
2003-02-05 03:24:47 +00:00
2003-02-28 08:42:02 +00:00
/* Couldn't find course in DWI path or alternative song folders */
if ( ! DoesFileExist ( sCoursePath ) )
2006-08-07 03:11:32 +00:00
{
sCoursePath = ADDITIONAL_SONGS_DIR + sCourseSuffix ;
if ( ! DoesFileExist ( sCoursePath ) )
return false ;
}
2002-08-30 20:49:56 +00:00
Course course ;
2005-07-31 05:41:32 +00:00
CourseLoaderCRS :: LoadFromCRSFile ( sCoursePath , course );
2003-02-14 21:42:44 +00:00
if ( course . GetEstimatedNumStages () <= 0 ) return false ;
2002-08-30 20:49:56 +00:00
2004-06-28 07:26:00 +00:00
Trail * pTrail = course . GetTrail ( GAMESTATE -> GetCurrentStyle () -> m_StepsType );
2004-05-23 00:53:20 +00:00
if ( pTrail -> m_vEntries . empty () )
2003-02-14 21:42:44 +00:00
return false ;
2003-04-23 07:28:24 +00:00
2004-05-23 00:53:20 +00:00
pSongOut = pTrail -> m_vEntries [ 0 ]. pSong ;
2006-11-12 03:26:25 +00:00
pStepsOut = pTrail -> m_vEntries [ 0 ]. pSteps ;
2003-04-23 07:28:24 +00:00
return true ;
2002-08-30 20:49:56 +00:00
}
2002-08-01 13:42:56 +00:00
2003-02-05 00:33:14 +00:00
/* Return true if n1 < n2. */
2003-08-03 00:13:55 +00:00
bool CompareNotesPointersForExtra ( const Steps * n1 , const Steps * n2 )
2003-02-05 00:33:14 +00:00
{
/* Equate CHALLENGE to HARD. */
2007-02-22 07:18:05 +00:00
Difficulty d1 = min ( n1 -> GetDifficulty (), Difficulty_Hard );
Difficulty d2 = min ( n2 -> GetDifficulty (), Difficulty_Hard );
2003-02-05 00:33:14 +00:00
if ( d1 < d2 ) return true ;
if ( d1 > d2 ) return false ;
/* n1 difficulty == n2 difficulty */
2004-04-18 07:34:37 +00:00
if ( StepsUtil :: CompareNotesPointersByMeter ( n1 , n2 )) return true ;
if ( StepsUtil :: CompareNotesPointersByMeter ( n2 , n1 )) return false ;
2003-02-05 00:33:14 +00:00
/* n1 meter == n2 meter */
2004-04-18 07:34:37 +00:00
return StepsUtil :: CompareNotesPointersByRadarValues ( n1 , n2 );
2003-02-05 00:33:14 +00:00
}
2007-04-10 18:24:06 +00:00
void SongManager :: GetExtraStageInfo ( bool bExtra2 , const Style * sd , Song *& pSongOut , Steps *& pStepsOut )
2002-08-01 13:42:56 +00:00
{
2006-01-22 01:00:06 +00:00
RString sGroup = GAMESTATE -> m_sPreferredSongGroup ;
2005-06-24 06:06:16 +00:00
if ( sGroup == GROUP_ALL )
2003-04-19 22:24:01 +00:00
{
2004-03-24 03:10:12 +00:00
if ( GAMESTATE -> m_pCurSong == NULL )
{
/* This normally shouldn't happen, but it's helpful to permit it for testing. */
2005-06-24 06:06:16 +00:00
LOG -> Warn ( "GetExtraStageInfo() called in GROUP_ALL, but GAMESTATE->m_pCurSong == NULL" );
2007-05-05 11:12:34 +00:00
GAMESTATE -> m_pCurSong . Set ( GetRandomSong () );
2004-03-24 03:10:12 +00:00
}
2003-07-30 05:34:47 +00:00
sGroup = GAMESTATE -> m_pCurSong -> m_sGroupName ;
2003-04-19 22:24:01 +00:00
}
2004-03-24 03:10:12 +00:00
2004-06-16 00:38:31 +00:00
ASSERT_M ( sGroup != "" , ssprintf ( "%p '%s' '%s'" ,
2005-02-27 21:55:30 +00:00
GAMESTATE -> m_pCurSong . Get (),
2003-07-31 01:27:54 +00:00
GAMESTATE -> m_pCurSong ? GAMESTATE -> m_pCurSong -> GetSongDir (). c_str () : "" ,
2004-03-24 03:10:12 +00:00
GAMESTATE -> m_pCurSong ? GAMESTATE -> m_pCurSong -> m_sGroupName . c_str () : "" ) );
2003-04-19 22:24:01 +00:00
2007-04-10 18:24:06 +00:00
if ( GetExtraStageInfoFromCourse ( bExtra2 , sGroup , pSongOut , pStepsOut ) )
2002-08-01 13:42:56 +00:00
return ;
// Choose a hard song for the extra stage
2003-08-03 00:13:55 +00:00
Song * pExtra1Song = NULL ; // the absolute hardest Song and Steps. Use this for extra stage 1.
Steps * pExtra1Notes = NULL ;
Song * pExtra2Song = NULL ; // a medium-hard Song and Steps. Use this for extra stage 2.
Steps * pExtra2Notes = NULL ;
2002-08-01 13:42:56 +00:00
2007-04-07 05:10:29 +00:00
const vector < Song *> & apSongs = GetSongs ( sGroup );
2002-10-31 02:46:42 +00:00
for ( unsigned s = 0 ; s < apSongs . size (); s ++ ) // foreach song
2002-08-01 13:42:56 +00:00
{
Song * pSong = apSongs [ s ];
2004-05-24 03:41:39 +00:00
vector < Steps *> apSteps ;
2006-06-13 01:10:37 +00:00
SongUtil :: GetSteps ( pSong , apSteps , sd -> m_StepsType );
2004-05-24 03:41:39 +00:00
for ( unsigned n = 0 ; n < apSteps . size (); n ++ ) // foreach Steps
2002-08-01 13:42:56 +00:00
{
2004-05-24 03:41:39 +00:00
Steps * pSteps = apSteps [ n ];
2002-08-01 13:42:56 +00:00
2004-05-24 03:41:39 +00:00
if ( pExtra1Notes == NULL || CompareNotesPointersForExtra ( pExtra1Notes , pSteps ) ) // pSteps is harder than pHardestNotes
2002-08-01 13:42:56 +00:00
{
pExtra1Song = pSong ;
2004-05-24 03:41:39 +00:00
pExtra1Notes = pSteps ;
2002-08-01 13:42:56 +00:00
}
2006-08-20 05:55:09 +00:00
// 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 )
2002-08-01 13:42:56 +00:00
continue ; // skip
2004-05-24 03:41:39 +00:00
if ( pExtra2Notes == NULL || CompareNotesPointersForExtra ( pExtra2Notes , pSteps ) ) // pSteps is harder than pHardestNotes
2002-08-01 13:42:56 +00:00
{
pExtra2Song = pSong ;
2004-05-24 03:41:39 +00:00
pExtra2Notes = pSteps ;
2002-08-01 13:42:56 +00:00
}
}
}
if ( pExtra2Song == NULL && pExtra1Song != NULL )
{
pExtra2Song = pExtra1Song ;
pExtra2Notes = pExtra1Notes ;
}
2003-08-07 06:16:17 +00:00
// 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
2002-08-01 13:42:56 +00:00
// had to play something before reaching the extra stage!
ASSERT ( pExtra2Song && pExtra1Song && pExtra2Notes && pExtra1Notes );
pSongOut = ( bExtra2 ? pExtra2Song : pExtra1Song );
2004-05-24 03:41:39 +00:00
pStepsOut = ( bExtra2 ? pExtra2Notes : pExtra1Notes );
2002-08-01 13:42:56 +00:00
}
2002-08-20 20:38:40 +00:00
2003-01-10 02:22:07 +00:00
Song * SongManager :: GetRandomSong ()
2002-08-20 20:38:40 +00:00
{
2004-02-01 23:06:07 +00:00
if ( m_pShuffledSongs . empty () )
2003-01-10 02:22:07 +00:00
return NULL ;
2002-08-20 20:38:40 +00:00
2004-02-02 05:49:18 +00:00
static int i = 0 ;
2004-02-01 23:06:07 +00:00
2005-05-22 15:05:15 +00:00
for ( int iThrowAway = 0 ; iThrowAway < 100 ; iThrowAway ++ )
{
i ++ ;
wrap ( i , m_pShuffledSongs . size () );
Song * pSong = m_pShuffledSongs [ i ];
if ( pSong -> IsTutorial () )
continue ;
if ( UNLOCKMAN -> SongIsLocked ( pSong ) )
continue ;
return pSong ;
}
return NULL ;
2004-02-01 23:06:07 +00:00
}
Course * SongManager :: GetRandomCourse ()
{
if ( m_pShuffledCourses . empty () )
return NULL ;
2004-02-02 05:49:18 +00:00
static int i = 0 ;
2004-02-01 23:06:07 +00:00
2005-05-22 15:05:15 +00:00
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 ;
}
return NULL ;
2002-08-20 20:38:40 +00:00
}
2002-11-08 08:17:59 +00:00
2007-04-07 06:13:00 +00:00
Song * SongManager :: GetSongFromDir ( RString sDir ) const
2002-11-08 08:17:59 +00:00
{
2003-12-10 09:44:16 +00:00
if ( sDir . Right ( 1 ) != "/" )
sDir += "/" ;
2002-12-02 05:25:44 +00:00
2004-02-08 01:05:53 +00:00
sDir . Replace ( '\\' , '/' );
2002-11-08 08:17:59 +00:00
for ( unsigned int i = 0 ; i < m_pSongs . size (); i ++ )
2002-12-02 05:25:44 +00:00
if ( sDir . CompareNoCase ( m_pSongs [ i ] -> GetSongDir ()) == 0 )
2002-11-08 08:17:59 +00:00
return m_pSongs [ i ];
return NULL ;
}
2003-01-21 05:14:59 +00:00
2007-04-07 06:13:00 +00:00
Course * SongManager :: GetCourseFromPath ( RString sPath ) const
2003-01-21 05:14:59 +00:00
{
2004-08-30 21:55:50 +00:00
if ( sPath == "" )
return NULL ;
2005-06-18 15:00:11 +00:00
FOREACH_CONST ( Course * , m_pCourses , c )
{
if ( sPath . CompareNoCase (( * c ) -> m_sPath ) == 0 )
return * c ;
}
2003-01-21 05:14:59 +00:00
return NULL ;
}
2003-01-22 05:29:27 +00:00
2007-04-07 06:13:00 +00:00
Course * SongManager :: GetCourseFromName ( RString sName ) const
2003-02-14 21:42:44 +00:00
{
2004-08-30 21:55:50 +00:00
if ( sName == "" )
return NULL ;
2003-02-14 21:42:44 +00:00
for ( unsigned int i = 0 ; i < m_pCourses . size (); i ++ )
2005-05-23 00:38:09 +00:00
if ( sName . CompareNoCase ( m_pCourses [ i ] -> GetDisplayFullTitle ()) == 0 )
2003-02-14 21:42:44 +00:00
return m_pCourses [ i ];
return NULL ;
}
2003-07-22 05:45:30 +00: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.
*/
2007-04-07 06:13:00 +00:00
Song * SongManager :: FindSong ( RString sPath ) const
2003-07-22 05:45:30 +00:00
{
2004-02-08 01:05:53 +00:00
sPath . Replace ( '\\' , '/' );
2006-01-22 01:00:06 +00:00
vector < RString > bits ;
2003-07-22 05:45:30 +00:00
split ( sPath , "/" , bits );
if ( bits . size () == 1 )
return FindSong ( "" , bits [ 0 ] );
else if ( bits . size () == 2 )
return FindSong ( bits [ 0 ], bits [ 1 ] );
return NULL ;
}
2007-04-07 06:13:00 +00:00
Song * SongManager :: FindSong ( RString sGroup , RString sSong ) const
2003-07-09 04:09:35 +00:00
{
// foreach song
2007-04-07 05:27:29 +00:00
const vector < Song *> & vSongs = GetSongs ( sGroup . empty () ? GROUP_ALL : sGroup );
FOREACH_CONST ( Song * , vSongs , s )
2003-07-09 04:09:35 +00:00
{
2006-05-01 21:49:59 +00:00
if ( ( * s ) -> Matches ( sGroup , sSong ) )
return * s ;
2003-07-09 04:09:35 +00:00
}
return NULL ;
}
2003-02-03 05:53:59 +00:00
2007-04-07 06:13:00 +00:00
Course * SongManager :: FindCourse ( RString sPath ) const
2003-07-17 12:14:19 +00:00
{
2006-05-01 21:49:59 +00:00
sPath . Replace ( '\\' , '/' );
vector < RString > bits ;
split ( sPath , "/" , bits );
if ( bits . size () == 1 )
return FindCourse ( "" , bits [ 0 ] );
else if ( bits . size () == 2 )
return FindCourse ( bits [ 0 ], bits [ 1 ] );
return NULL ;
}
2007-04-07 06:13:00 +00:00
Course * SongManager :: FindCourse ( RString sGroup , RString sName ) const
2006-05-01 21:49:59 +00:00
{
FOREACH_CONST ( Course * , m_pCourses , c )
2003-07-17 12:14:19 +00:00
{
2006-05-01 21:49:59 +00:00
if ( ( * c ) -> Matches ( sGroup , sName ) )
return * c ;
2003-07-17 12:14:19 +00:00
}
return NULL ;
2003-07-21 21:45:59 +00:00
}
2006-03-20 01:04:46 +00:00
void SongManager :: UpdatePopular ()
2003-07-21 21:45:59 +00:00
{
2004-02-01 23:06:07 +00:00
// update players best
2005-09-04 23:29:31 +00:00
vector < Song *> apBestSongs = m_pSongs ;
for ( unsigned j = 0 ; j < apBestSongs . size () ; ++ j )
{
bool bFiltered = false ;
/* Filter out hidden songs. */
2007-05-08 01:47:44 +00:00
if ( apBestSongs [ j ] -> GetDisplayed () != Song :: SHOW_ALWAYS )
2005-09-04 23:29:31 +00:00
bFiltered = true ;
/* Filter out locked songs. */
2006-06-14 05:43:50 +00:00
// XXX Hack, this depends on UNLOCKMAN being around.
if ( UNLOCKMAN && UNLOCKMAN -> SongIsLocked ( apBestSongs [ j ]) )
2005-09-04 23:29:31 +00:00
bFiltered = true ;
if ( ! bFiltered )
continue ;
/* Remove it. */
swap ( apBestSongs [ j ], apBestSongs . back () );
apBestSongs . erase ( apBestSongs . end () - 1 );
-- j ;
}
SongUtil :: SortSongPointerArrayByTitle ( apBestSongs );
vector < Course *> apBestCourses [ NUM_CourseType ];
2007-02-23 22:29:42 +00:00
FOREACH_ENUM ( CourseType , ct )
2005-09-04 23:29:31 +00:00
{
GetCourses ( ct , apBestCourses [ ct ], PREFSMAN -> m_bAutogenGroupCourses );
CourseUtil :: SortCoursePointerArrayByTitle ( apBestCourses [ ct ] );
}
2007-04-10 22:41:45 +00:00
m_pPopularSongs = apBestSongs ;
SongUtil :: SortSongPointerArrayByNumPlays ( m_pPopularSongs , ProfileSlot_Machine , true );
FOREACH_CourseType ( ct )
2003-11-14 21:52:05 +00:00
{
2007-04-10 22:41:45 +00:00
vector < Course *> & vpCourses = m_pPopularCourses [ ct ];
vpCourses = apBestCourses [ ct ];
CourseUtil :: SortCoursePointerArrayByNumPlays ( vpCourses , ProfileSlot_Machine , true );
2003-11-14 21:52:05 +00:00
}
2004-02-27 21:28:15 +00:00
}
2004-02-01 23:06:07 +00:00
2004-02-27 21:28:15 +00:00
void SongManager :: UpdateShuffled ()
{
2004-02-01 23:06:07 +00:00
// update shuffled
m_pShuffledSongs = m_pSongs ;
2007-04-07 10:04:08 +00:00
random_shuffle ( m_pShuffledSongs . begin (), m_pShuffledSongs . end (), g_RandomNumberGenerator );
2004-02-01 23:06:07 +00:00
m_pShuffledCourses = m_pCourses ;
2007-04-07 10:04:08 +00:00
random_shuffle ( m_pShuffledCourses . begin (), m_pShuffledCourses . end (), g_RandomNumberGenerator );
2003-07-21 21:45:59 +00:00
}
2006-03-20 01:04:46 +00:00
void SongManager :: UpdatePreferredSort ()
{
2006-05-01 12:45:07 +00:00
ASSERT ( UNLOCKMAN );
{
2006-05-01 21:49:59 +00:00
m_vPreferredSongSort . clear ();
RString sFile = THEME -> GetPathO ( "SongManager" , "PreferredSongs.txt" );
RageFile file ;
if ( ! file . Open ( sFile ) )
return ;
vector < Song *> vpSongs ;
RString sLine ;
while ( file . GetLine ( sLine ) )
2006-03-20 01:04:46 +00:00
{
2006-05-01 21:49:59 +00:00
bool bSectionDivider = sLine . find ( "---" ) != RString :: npos ;
if ( bSectionDivider )
2006-03-20 01:04:46 +00:00
{
2006-05-01 21:49:59 +00:00
if ( ! vpSongs . empty () )
{
m_vPreferredSongSort . push_back ( vpSongs );
vpSongs . clear ();
}
}
else
{
Song * pSong = NULL ;
if ( ! sLine . empty () )
pSong = FindSong ( sLine );
2007-05-08 01:49:21 +00:00
if ( pSong && pSong -> GetDisplayed () != Song :: SHOW_NEVER )
2006-05-01 21:49:59 +00:00
vpSongs . push_back ( pSong );
2006-03-20 01:04:46 +00:00
}
}
2006-05-01 21:49:59 +00:00
if ( ! vpSongs . empty () )
2006-03-20 01:04:46 +00:00
{
2006-05-01 21:49:59 +00:00
m_vPreferredSongSort . push_back ( vpSongs );
vpSongs . clear ();
2006-03-20 01:04:46 +00:00
}
2006-05-01 12:45:07 +00:00
2006-05-03 21:33:19 +00:00
if ( MOVE_UNLOCKS_TO_BOTTOM_OF_PREFERRED_SORT . GetValue () )
2006-05-01 12:45:07 +00:00
{
2006-05-03 21:33:19 +00:00
// move all unlock songs to a group at the bottom
vector < Song *> vpUnlockSongs ;
FOREACH ( UnlockEntry , UNLOCKMAN -> m_UnlockEntries , ue )
2006-05-01 12:45:07 +00:00
{
2006-05-03 21:33:19 +00:00
if ( ue -> m_Type == UnlockRewardType_Song )
if ( ue -> m_pSong )
vpUnlockSongs . push_back ( ue -> m_pSong );
}
FOREACH ( SongPointerVector , m_vPreferredSongSort , v )
{
for ( int i = v -> size () - 1 ; i >= 0 ; i -- )
2006-05-01 21:49:59 +00:00
{
2006-05-03 21:33:19 +00:00
Song * pSong = ( * v )[ i ];
if ( find ( vpUnlockSongs . begin (), vpUnlockSongs . end (), pSong ) != vpUnlockSongs . end () )
{
v -> erase ( v -> begin () + i );
}
2006-05-01 21:49:59 +00:00
}
2006-05-01 12:45:07 +00:00
}
2006-05-03 21:33:19 +00:00
m_vPreferredSongSort . push_back ( vpUnlockSongs );
// prune empty groups
for ( int i = m_vPreferredSongSort . size () - 1 ; i >= 0 ; i -- )
if ( m_vPreferredSongSort [ i ]. empty () )
m_vPreferredSongSort . erase ( m_vPreferredSongSort . begin () + i );
FOREACH ( SongPointerVector , m_vPreferredSongSort , i )
FOREACH ( Song * , * i , j )
ASSERT ( * j );
2006-05-01 12:45:07 +00:00
}
}
2006-05-01 21:49:59 +00:00
{
m_vPreferredCourseSort . clear ();
RString sFile = THEME -> GetPathO ( "SongManager" , "PreferredCourses.txt" );
RageFile file ;
if ( ! file . Open ( sFile ) )
return ;
vector < Course *> vpCourses ;
RString sLine ;
while ( file . GetLine ( sLine ) )
{
bool bSectionDivider = sLine . find ( "---" ) != RString :: npos ;
if ( bSectionDivider )
{
if ( ! vpCourses . empty () )
{
m_vPreferredCourseSort . push_back ( vpCourses );
vpCourses . clear ();
}
}
else
{
Course * pCourse = NULL ;
if ( ! sLine . empty () )
pCourse = FindCourse ( sLine );
if ( pCourse )
vpCourses . push_back ( pCourse );
}
}
if ( ! vpCourses . empty () )
{
m_vPreferredCourseSort . push_back ( vpCourses );
vpCourses . clear ();
}
2006-05-03 21:33:19 +00:00
if ( MOVE_UNLOCKS_TO_BOTTOM_OF_PREFERRED_SORT . GetValue () )
2006-05-01 21:49:59 +00:00
{
2006-05-03 21:33:19 +00:00
// move all unlock Courses to a group at the bottom
vector < Course *> vpUnlockCourses ;
FOREACH ( UnlockEntry , UNLOCKMAN -> m_UnlockEntries , ue )
2006-05-01 21:49:59 +00:00
{
2006-05-03 21:33:19 +00:00
if ( ue -> m_Type == UnlockRewardType_Course )
if ( ue -> m_pCourse )
vpUnlockCourses . push_back ( ue -> m_pCourse );
}
FOREACH ( CoursePointerVector , m_vPreferredCourseSort , v )
{
for ( int i = v -> size () - 1 ; i >= 0 ; i -- )
2006-05-01 21:49:59 +00:00
{
2006-05-03 21:33:19 +00:00
Course * pCourse = ( * v )[ i ];
if ( find ( vpUnlockCourses . begin (), vpUnlockCourses . end (), pCourse ) != vpUnlockCourses . end () )
{
v -> erase ( v -> begin () + i );
}
2006-05-01 21:49:59 +00:00
}
}
2006-05-03 21:33:19 +00:00
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 );
FOREACH ( CoursePointerVector , m_vPreferredCourseSort , i )
FOREACH ( Course * , * i , j )
ASSERT ( * j );
2006-05-01 21:49:59 +00:00
}
}
2006-03-20 01:04:46 +00:00
}
2004-02-27 21:28:15 +00:00
void SongManager :: SortSongs ()
{
2004-04-18 07:34:37 +00:00
SongUtil :: SortSongPointerArrayByTitle ( m_pSongs );
2004-02-27 21:28:15 +00:00
}
2003-08-04 09:18:26 +00:00
void SongManager :: UpdateRankingCourses ()
{
2005-05-19 20:32:10 +00:00
/* Updating the ranking courses data is fairly expensive
* since it involves comparing strings. Do so sparingly. */
2006-01-22 01:00:06 +00:00
vector < RString > RankingCourses ;
2003-08-04 09:18:26 +00:00
split ( THEME -> GetMetric ( "ScreenRanking" , "CoursesToShow" ), "," , RankingCourses );
2005-05-19 20:32:10 +00:00
for ( unsigned i = 0 ; i < m_pCourses . size (); i ++ )
2003-08-04 09:18:26 +00:00
{
2005-05-19 20:32:10 +00:00
if ( m_pCourses [ i ] -> GetEstimatedNumStages () > 7 )
2004-05-23 00:53:20 +00:00
m_pCourses [ i ] -> m_SortOrder_Ranking = 3 ;
2003-08-04 09:18:26 +00:00
else
2004-05-23 00:53:20 +00:00
m_pCourses [ i ] -> m_SortOrder_Ranking = 2 ;
2003-08-04 09:18:26 +00:00
2005-05-19 20:32:10 +00:00
for ( unsigned j = 0 ; j < RankingCourses . size (); j ++ )
if ( ! RankingCourses [ j ]. CompareNoCase ( m_pCourses [ i ] -> m_sPath ) )
2004-05-23 00:53:20 +00:00
m_pCourses [ i ] -> m_SortOrder_Ranking = 1 ;
2003-08-04 09:18:26 +00:00
}
2003-08-10 03:23:17 +00:00
}
2003-08-17 18:03:02 +00:00
2005-07-31 05:41:32 +00:00
void SongManager :: RefreshCourseGroupInfo ()
{
m_mapCourseGroupToInfo . clear ();
FOREACH_CONST ( Course * , m_pCourses , c )
{
m_mapCourseGroupToInfo [( * c ) -> m_sGroupName ]; // insert
}
// TODO: Search for course group banners
2006-01-22 01:00:06 +00:00
FOREACHM ( RString , CourseGroupInfo , m_mapCourseGroupToInfo , iter )
2005-07-31 05:41:32 +00:00
{
}
}
2007-05-27 17:59:41 +00:00
void SongManager :: LoadStepEditsFromProfileDir ( const RString & sProfileDir , ProfileSlot slot )
2005-04-24 23:39:35 +00:00
{
2004-02-08 01:05:53 +00:00
{
2005-07-31 05:41:32 +00:00
//
// Load all edit steps
//
2006-01-22 01:00:06 +00:00
RString sDir = sProfileDir + EDIT_STEPS_SUBDIR ;
2004-04-23 02:08:11 +00:00
2006-01-22 01:00:06 +00:00
vector < RString > vsFiles ;
2005-07-31 05:41:32 +00:00
GetDirListing ( sDir + "*.edit" , vsFiles , false , true );
int iNumEditsLoaded = GetNumEditsLoadedFromProfile ( slot );
2005-08-03 03:22:14 +00:00
int size = min ( ( int ) vsFiles . size (), MAX_EDIT_STEPS_PER_PROFILE - iNumEditsLoaded );
2005-07-31 05:41:32 +00:00
for ( int i = 0 ; i < size ; i ++ )
2004-02-08 01:05:53 +00:00
{
2006-01-22 01:00:06 +00:00
RString fn = vsFiles [ i ];
2005-04-24 23:39:35 +00:00
2006-03-27 20:17:05 +00:00
SMLoader :: LoadEditFromFile ( fn , slot , true );
2005-07-31 05:41:32 +00:00
}
}
2007-05-27 17:59:41 +00:00
}
2005-07-31 05:41:32 +00:00
2007-05-27 17:59:41 +00:00
void SongManager :: LoadCourseEditsFromProfileDir ( const RString & sProfileDir , ProfileSlot slot )
{
2005-07-31 05:41:32 +00:00
{
//
// Load all edit courses
//
2006-01-22 01:00:06 +00:00
RString sDir = sProfileDir + EDIT_COURSES_SUBDIR ;
2005-07-31 05:41:32 +00:00
2006-01-22 01:00:06 +00:00
vector < RString > vsFiles ;
2005-07-31 05:41:32 +00:00
GetDirListing ( sDir + "*.crs" , vsFiles , false , true );
int iNumEditsLoaded = GetNumEditsLoadedFromProfile ( slot );
2005-08-03 03:22:14 +00:00
int size = min ( ( int ) vsFiles . size (), MAX_EDIT_COURSES_PER_PROFILE - iNumEditsLoaded );
2005-07-31 05:41:32 +00:00
for ( int i = 0 ; i < size ; i ++ )
{
2006-01-22 01:00:06 +00:00
RString fn = vsFiles [ i ];
2005-07-31 05:41:32 +00:00
2006-03-27 20:17:05 +00:00
CourseLoaderCRS :: LoadEditFromFile ( fn , slot );
2005-07-31 05:41:32 +00:00
}
2004-02-08 01:05:53 +00:00
}
}
2005-04-25 02:27:17 +00: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 ];
vector < Steps *> apSteps ;
2006-06-13 01:10:37 +00:00
SongUtil :: GetSteps ( pSong , apSteps );
2005-04-25 02:27:17 +00:00
for ( unsigned i = 0 ; i < apSteps . size (); ++ i )
{
const Steps * pSteps = apSteps [ i ];
if ( pSteps -> WasLoadedFromProfile () && pSteps -> GetLoadedFromProfileSlot () == slot )
++ iCount ;
}
}
return iCount ;
}
2005-04-25 00:09:59 +00:00
void SongManager :: FreeAllLoadedFromProfile ( ProfileSlot slot )
2004-02-08 01:05:53 +00:00
{
2007-01-19 06:36:49 +00:00
/* Profile courses may refer to profile steps, so free profile courses first. */
2005-08-11 20:51:15 +00:00
vector < Course *> apToDelete ;
2005-07-31 05:41:32 +00:00
FOREACH ( Course * , m_pCourses , c )
2004-02-08 01:05:53 +00:00
{
2005-08-11 20:51:15 +00:00
Course * pCourse = * c ;
2006-10-07 03:32:16 +00:00
if ( pCourse -> GetLoadedFromProfileSlot () == ProfileSlot_Invalid )
2005-08-11 20:51:15 +00:00
continue ;
2006-10-07 03:32:16 +00:00
if ( slot == ProfileSlot_Invalid || pCourse -> GetLoadedFromProfileSlot () == slot )
2005-08-11 20:51:15 +00:00
apToDelete . push_back ( * c );
2004-02-08 01:05:53 +00:00
}
2004-11-24 16:59:57 +00:00
2007-01-19 06:39:43 +00:00
/* We don't use DeleteCourse here, so we don't UpdatePopular and UpdateShuffled
* repeatedly. */
2005-08-11 20:51:15 +00:00
for ( unsigned i = 0 ; i < apToDelete . size (); ++ i )
2007-01-19 06:39:43 +00:00
{
vector < Course *>:: iterator iter = find ( m_pCourses . begin (), m_pCourses . end (), apToDelete [ i ] );
ASSERT ( iter != m_pCourses . end () );
m_pCourses . erase ( iter );
delete apToDelete [ i ];
}
2005-08-11 20:51:15 +00:00
2007-01-19 06:39:43 +00:00
/* Popular and Shuffled may refer to courses that we just freed. */
UpdatePopular ();
UpdateShuffled ();
RefreshCourseGroupInfo ();
/* Free profile steps. */
2007-05-31 18:24:22 +00:00
set < Steps *> setInUse ;
if ( STATSMAN )
STATSMAN -> GetStepsInUse ( setInUse );
2007-01-19 06:36:49 +00:00
FOREACH ( Song * , m_pSongs , s )
2007-05-31 18:24:22 +00:00
( * s ) -> FreeAllLoadedFromProfile ( slot , & setInUse );
2004-02-08 01:05:53 +00:00
}
2005-03-08 01:46:57 +00:00
int SongManager :: GetNumStepsLoadedFromProfile ()
2005-03-07 05:20:32 +00:00
{
2005-03-08 01:46:57 +00:00
int iCount = 0 ;
FOREACH ( Song * , m_pSongs , s )
2005-03-07 05:20:32 +00:00
{
2005-03-08 01:46:57 +00:00
vector < Steps *> vpAllSteps = ( * s ) -> GetAllSteps ();
FOREACH ( Steps * , vpAllSteps , ss )
{
2006-10-07 03:32:16 +00:00
if ( ( * ss ) -> GetLoadedFromProfileSlot () != ProfileSlot_Invalid )
2005-03-08 01:46:57 +00:00
iCount ++ ;
}
2005-03-07 05:20:32 +00:00
}
2005-03-08 01:46:57 +00:00
return iCount ;
2005-03-07 05:20:32 +00:00
}
2005-02-21 17:26:43 +00:00
2006-08-17 01:58:58 +00:00
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. */
if ( ! ( * it ) -> IsPlayableIn ( GAMESTATE -> GetCurrentStyle () -> m_StepsType ) )
continue ;
if ( ( * it ) -> GetPlayMode () != pm )
continue ;
++ n ;
}
return - 1 ;
}
2005-02-21 17:26:43 +00:00
// lua start
#include "LuaBinding.h"
2005-06-20 05:02:03 +00:00
class LunaSongManager : public Luna < SongManager >
2005-02-21 17:26:43 +00:00
{
public :
2005-02-22 23:08:46 +00:00
static int GetAllSongs ( T * p , lua_State * L )
{
2007-04-07 05:36:40 +00:00
const vector < Song *> & v = p -> GetSongs ();
2005-03-13 00:01:05 +00:00
LuaHelpers :: CreateTableFromArray < Song *> ( v , L );
2005-02-22 23:08:46 +00:00
return 1 ;
}
2005-02-21 17:26:43 +00:00
static int GetAllCourses ( T * p , lua_State * L )
{
2005-02-22 23:08:46 +00:00
vector < Course *> v ;
p -> GetAllCourses ( v , BArg ( 1 ) );
2005-03-13 00:01:05 +00:00
LuaHelpers :: CreateTableFromArray < Course *> ( v , L );
2005-02-21 17:26:43 +00:00
return 1 ;
}
2005-05-22 15:05:15 +00:00
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 ; }
2006-05-16 00:47:02 +00:00
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 ; }
2006-05-03 21:33:19 +00:00
static int GetNumUnlockedSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , p -> GetNumUnlockedSongs () ); return 1 ; }
2006-05-16 00:47:02 +00:00
static int GetNumSelectableAndUnlockedSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , p -> GetNumSelectableAndUnlockedSongs () ); return 1 ; }
static int GetNumAdditionalSongs ( T * p , lua_State * L ) { lua_pushnumber ( L , p -> GetNumAdditionalSongs () ); return 1 ; }
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 ; }
2006-08-14 20:05:35 +00:00
static int GetNumAdditionalCourses ( T * p , lua_State * L ){ lua_pushnumber ( L , p -> GetNumAdditionalCourses () ); return 1 ; }
2006-05-16 00:47:02 +00:00
static int GetNumCourseGroups ( T * p , lua_State * L ) { lua_pushnumber ( L , p -> GetNumCourseGroups () ); return 1 ; }
2005-12-05 19:08:42 +00:00
static int GetSongFromSteps ( T * p , lua_State * L )
{
Song * pSong = NULL ;
if ( lua_isnil ( L , 1 ) ) { pSong = p -> GetSongFromSteps ( NULL ); }
else { Steps * pSteps = Luna < Steps >:: check ( L , 1 ); pSong = p -> GetSongFromSteps ( pSteps ); }
if ( pSong ) pSong -> PushSelf ( L );
else lua_pushnil ( L );
return 1 ;
}
2006-08-18 00:11:30 +00:00
static int GetExtraStageInfo ( T * p , lua_State * L )
{
bool bExtra2 = BArg ( 1 );
const Style * pStyle = Luna < Style >:: check ( L , 2 );
Song * pSong ;
Steps * pSteps ;
2007-04-10 18:24:06 +00:00
p -> GetExtraStageInfo ( bExtra2 , pStyle , pSong , pSteps );
2006-08-18 00:11:30 +00:00
pSong -> PushSelf ( L );
pSteps -> PushSelf ( L );
2007-04-10 18:24:34 +00:00
return 2 ;
2006-08-18 00:11:30 +00:00
}
2007-04-07 13:00:14 +00:00
DEFINE_METHOD ( GetSongColor , GetSongColor ( Luna < Song >:: check ( L , 1 ) ) )
2005-02-21 17:26:43 +00:00
2007-04-07 22:19:12 +00:00
2006-09-27 20:30:29 +00:00
LunaSongManager ()
2005-02-21 17:26:43 +00:00
{
2005-09-10 02:47:04 +00:00
ADD_METHOD ( GetAllSongs );
ADD_METHOD ( GetAllCourses );
ADD_METHOD ( FindSong );
ADD_METHOD ( FindCourse );
ADD_METHOD ( GetRandomSong );
ADD_METHOD ( GetRandomCourse );
ADD_METHOD ( GetNumSongs );
2006-05-03 21:33:19 +00:00
ADD_METHOD ( GetNumUnlockedSongs );
2006-05-16 00:47:02 +00:00
ADD_METHOD ( GetNumSelectableAndUnlockedSongs );
2006-05-03 21:33:19 +00:00
ADD_METHOD ( GetNumAdditionalSongs );
2005-09-10 02:47:04 +00:00
ADD_METHOD ( GetNumSongGroups );
ADD_METHOD ( GetNumCourses );
2006-08-14 20:05:35 +00:00
ADD_METHOD ( GetNumAdditionalCourses );
2005-09-10 02:47:04 +00:00
ADD_METHOD ( GetNumCourseGroups );
2005-12-05 19:08:42 +00:00
ADD_METHOD ( GetSongFromSteps );
2006-08-18 00:11:30 +00:00
ADD_METHOD ( GetExtraStageInfo );
2007-04-07 13:00:14 +00:00
ADD_METHOD ( GetSongColor );
2005-02-21 17:26:43 +00:00
}
};
LUA_REGISTER_CLASS ( SongManager )
// lua end
2004-06-08 01:24:17 +00:00
/*
* (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.
*/