2012-07-01 12:24:39 -05:00
# include "global.h"
# include "Song.h"
# include "Steps.h"
# include "RageUtil.h"
# include "RageLog.h"
# include "NoteData.h"
# include "RageSoundReader_FileReader.h"
# include "RageSurface_Load.h"
# include "SongCacheIndex.h"
# include "GameManager.h"
# include "PrefsManager.h"
# include "Style.h"
# include "FontCharAliases.h"
# include "TitleSubstitution.h"
# include "BannerCache.h"
//#include "BackgroundCache.h"
# include "Sprite.h"
# include "RageFileManager.h"
# include "RageSurface.h"
2015-03-31 15:20:37 -06:00
# include "RageTextureManager.h"
2012-07-01 12:24:39 -05:00
# include "NoteDataUtil.h"
# include "SongUtil.h"
2015-03-31 15:20:37 -06:00
# include "SongManager.h"
2012-07-01 12:24:39 -05:00
# include "StepsUtil.h"
# include "Foreach.h"
# include "BackgroundUtil.h"
# include "SpecialFiles.h"
# include "NotesLoader.h"
# include "NotesLoaderSM.h"
# include "NotesLoaderSSC.h"
# include "NotesWriterDWI.h"
2012-07-01 15:27:50 -05:00
# include "NotesWriterJson.h"
2012-07-01 12:24:39 -05:00
# include "NotesWriterSM.h"
# include "NotesWriterSSC.h"
# include "UnlockManager.h"
# include "LyricsLoader.h"
2014-11-09 00:04:47 -07:00
# include "ActorUtil.h"
2012-07-01 12:24:39 -05:00
# include <time.h>
# include <set>
# include <float.h>
/**
* @brief The internal version of the cache for StepMania.
*
* Increment this value to invalidate the current cache. */
2015-04-07 12:47:51 -06:00
const int FILE_CACHE_VERSION = 226 ;
2012-07-01 12:24:39 -05:00
/** @brief How long does a song sample last by default? */
const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f ;
static Preference < float > g_fLongVerSongSeconds ( " LongVerSongSeconds " , 60 * 2.5f ) ;
static Preference < float > g_fMarathonVerSongSeconds ( " MarathonVerSongSeconds " , 60 * 5.f ) ;
static Preference < bool > g_BackUpAllSongSaves ( " BackUpAllSongSaves " , false ) ;
static const char * InstrumentTrackNames [ ] = {
" Guitar " ,
" Rhythm " ,
" Bass " ,
} ;
XToString ( InstrumentTrack ) ;
StringToX ( InstrumentTrack ) ;
Song : : Song ( )
{
FOREACH_BackgroundLayer ( i )
m_BackgroundChanges [ i ] = AutoPtrCopyOnWrite < VBackgroundChange > ( new VBackgroundChange ) ;
m_ForegroundChanges = AutoPtrCopyOnWrite < VBackgroundChange > ( new VBackgroundChange ) ;
m_LoadedFromProfile = ProfileSlot_Invalid ;
m_fVersion = STEPFILE_VERSION_NUMBER ;
m_fMusicSampleStartSeconds = - 1 ;
m_fMusicSampleLengthSeconds = DEFAULT_MUSIC_SAMPLE_LENGTH ;
m_fMusicLengthSeconds = 0 ;
firstSecond = - 1 ;
lastSecond = - 1 ;
specifiedLastSecond = - 1 ;
m_SelectionDisplay = SHOW_ALWAYS ;
m_bEnabled = true ;
m_DisplayBPMType = DISPLAY_BPM_ACTUAL ;
m_fSpecifiedBPMMin = 0 ;
m_fSpecifiedBPMMax = 0 ;
m_bIsSymLink = false ;
m_bHasMusic = false ;
m_bHasBanner = false ;
m_bHasBackground = false ;
2015-02-27 10:36:17 -07:00
m_loaded_from_autosave = false ;
2012-07-01 12:24:39 -05:00
}
Song : : ~ Song ( )
{
FOREACH ( Steps * , m_vpSteps , s )
SAFE_DELETE ( * s ) ;
m_vpSteps . clear ( ) ;
2014-10-20 05:14:54 -06:00
FOREACH ( Steps * , m_UnknownStyleSteps , s )
{
SAFE_DELETE ( * s ) ;
}
m_UnknownStyleSteps . clear ( ) ;
2012-07-01 12:24:39 -05:00
// It's the responsibility of the owner of this Song to make sure
// that all pointers to this Song and its Steps are invalidated.
}
void Song : : DetachSteps ( )
{
m_vpSteps . clear ( ) ;
FOREACH_ENUM ( StepsType , st )
m_vpStepsByType [ st ] . clear ( ) ;
2014-10-20 05:14:54 -06:00
m_UnknownStyleSteps . clear ( ) ;
2012-07-01 12:24:39 -05:00
}
float Song : : GetFirstSecond ( ) const
{
return this - > firstSecond ;
}
float Song : : GetFirstBeat ( ) const
{
return this - > m_SongTiming . GetBeatFromElapsedTime ( this - > firstSecond ) ;
}
float Song : : GetLastSecond ( ) const
{
return this - > lastSecond ;
}
float Song : : GetLastBeat ( ) const
{
return this - > m_SongTiming . GetBeatFromElapsedTime ( this - > lastSecond ) ;
}
float Song : : GetSpecifiedLastSecond ( ) const
{
return this - > specifiedLastSecond ;
}
float Song : : GetSpecifiedLastBeat ( ) const
{
return this - > m_SongTiming . GetBeatFromElapsedTime ( this - > specifiedLastSecond ) ;
}
void Song : : SetFirstSecond ( const float f )
{
this - > firstSecond = f ;
}
void Song : : SetLastSecond ( const float f )
{
this - > lastSecond = f ;
}
void Song : : SetSpecifiedLastSecond ( const float f )
{
this - > specifiedLastSecond = f ;
}
// Reset to an empty song.
void Song : : Reset ( )
{
FOREACH ( Steps * , m_vpSteps , s )
SAFE_DELETE ( * s ) ;
m_vpSteps . clear ( ) ;
FOREACH_ENUM ( StepsType , st )
m_vpStepsByType [ st ] . clear ( ) ;
2014-10-20 05:14:54 -06:00
FOREACH ( Steps * , m_UnknownStyleSteps , s )
{
SAFE_DELETE ( * s ) ;
}
m_UnknownStyleSteps . clear ( ) ;
2012-07-01 12:24:39 -05:00
Song empty ;
* this = empty ;
// It's the responsibility of the owner of this Song to make sure
// that all pointers to this Song and its Steps are invalidated.
}
void Song : : AddBackgroundChange ( BackgroundLayer iLayer , BackgroundChange seg )
{
// Delete old background change at this start beat, if any.
FOREACH ( BackgroundChange , GetBackgroundChanges ( iLayer ) , bgc )
{
if ( bgc - > m_fStartBeat = = seg . m_fStartBeat )
{
GetBackgroundChanges ( iLayer ) . erase ( bgc ) ;
break ;
}
}
ASSERT ( iLayer > = 0 & & iLayer < NUM_BackgroundLayer ) ;
BackgroundUtil : : AddBackgroundChange ( GetBackgroundChanges ( iLayer ) , seg ) ;
}
void Song : : AddForegroundChange ( BackgroundChange seg )
{
BackgroundUtil : : AddBackgroundChange ( GetForegroundChanges ( ) , seg ) ;
}
void Song : : AddLyricSegment ( LyricSegment seg )
{
m_LyricSegments . push_back ( seg ) ;
}
Steps * Song : : CreateSteps ( )
{
2013-01-20 19:23:28 -05:00
Steps * pSteps = new Steps ( this ) ;
2012-07-01 12:24:39 -05:00
InitSteps ( pSteps ) ;
return pSteps ;
}
void Song : : InitSteps ( Steps * pSteps )
{
2013-01-23 14:51:18 -05:00
// TimingData is initially empty (i.e. defaults to song timing)
2012-07-01 12:24:39 -05:00
pSteps - > m_sAttackString = this - > m_sAttackString ;
pSteps - > m_Attacks = this - > m_Attacks ;
pSteps - > SetDisplayBPM ( this - > m_DisplayBPMType ) ;
pSteps - > SetMinBPM ( this - > m_fSpecifiedBPMMin ) ;
pSteps - > SetMaxBPM ( this - > m_fSpecifiedBPMMax ) ;
}
void Song : : GetDisplayBpms ( DisplayBpms & AddTo ) const
{
if ( m_DisplayBPMType = = DISPLAY_BPM_SPECIFIED )
{
AddTo . Add ( m_fSpecifiedBPMMin ) ;
AddTo . Add ( m_fSpecifiedBPMMax ) ;
}
else
{
float fMinBPM , fMaxBPM ;
m_SongTiming . GetActualBPM ( fMinBPM , fMaxBPM ) ;
AddTo . Add ( fMinBPM ) ;
AddTo . Add ( fMaxBPM ) ;
}
}
const BackgroundChange & Song : : GetBackgroundAtBeat ( BackgroundLayer iLayer , float fBeat ) const
{
for ( unsigned i = 0 ; i < GetBackgroundChanges ( iLayer ) . size ( ) - 1 ; i + + )
if ( GetBackgroundChanges ( iLayer ) [ i + 1 ] . m_fStartBeat > fBeat )
return GetBackgroundChanges ( iLayer ) [ i ] ;
return GetBackgroundChanges ( iLayer ) [ 0 ] ;
}
RString Song : : GetCacheFilePath ( ) const
{
return SongCacheIndex : : GetCacheFilePath ( " Songs " , m_sSongDir ) ;
}
// Get a path to the SM containing data for this song. It might be a cache file.
const RString & Song : : GetSongFilePath ( ) const
{
ASSERT_M ( ! m_sSongFileName . empty ( ) ,
ssprintf ( " The song %s has no filename associated with it! " ,
this - > m_sMainTitle . c_str ( ) ) ) ;
return m_sSongFileName ;
}
/* Hack: This should be a parameter to TidyUpData, but I don't want to pull in
* <set> into Song.h, which is heavily used. */
static set < RString > BlacklistedImages ;
/* If PREFSMAN->m_bFastLoad is true, always load from cache if possible.
* Don't read the contents of sDir if we can avoid it. That means we can't call
* HasMusic(), HasBanner() or GetHashForDirectory().
* If true, check the directory hash and reload the song from scratch if it's changed.
*/
2015-02-27 10:36:17 -07:00
bool Song : : LoadFromSongDir ( RString sDir , bool load_autosave )
2012-07-01 12:24:39 -05:00
{
// LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.c_str() );
ASSERT_M ( sDir ! = " " , " Songs can't be loaded from an empty directory! " ) ;
// make sure there is a trailing slash at the end of sDir
if ( sDir . Right ( 1 ) ! = " / " )
sDir + = " / " ;
// save song dir
m_sSongDir = sDir ;
// save group name
vector < RString > sDirectoryParts ;
split ( m_sSongDir , " / " , sDirectoryParts , false ) ;
ASSERT ( sDirectoryParts . size ( ) > = 4 ) ; /* e.g. "/Songs/Slow/Taps/" */
m_sGroupName = sDirectoryParts [ sDirectoryParts . size ( ) - 3 ] ; // second from last item
ASSERT ( m_sGroupName ! = " " ) ;
// First, look in the cache for this song (without loading NoteData)
unsigned uCacheHash = SONGINDEX - > GetCacheHash ( m_sSongDir ) ;
bool bUseCache = true ;
RString sCacheFilePath = GetCacheFilePath ( ) ;
if ( ! DoesFileExist ( sCacheFilePath ) )
2015-03-23 11:35:30 -06:00
{ bUseCache = false ; }
2015-03-30 17:45:52 -06:00
else if ( ! PREFSMAN - > m_bFastLoad & & GetHashForDirectory ( m_sSongDir ) ! = uCacheHash )
2015-03-23 11:35:30 -06:00
{ bUseCache = false ; } // this cache is out of date
2015-03-30 17:45:52 -06:00
else if ( load_autosave )
2015-02-27 10:36:17 -07:00
{ bUseCache = false ; }
2012-07-01 12:24:39 -05:00
if ( bUseCache )
{
/*
LOG->Trace("Loading '%s' from cache file '%s'.",
m_sSongDir.c_str(),
GetCacheFilePath().c_str());
*/
SSCLoader loaderSSC ;
bool bLoadedFromSSC = loaderSSC . LoadFromSimfile ( sCacheFilePath , * this , true ) ;
if ( ! bLoadedFromSSC )
{
// load from .sm
SMLoader loaderSM ;
loaderSM . LoadFromSimfile ( sCacheFilePath , * this , true ) ;
loaderSM . TidyUpData ( * this , true ) ;
}
2015-04-10 22:40:49 -06:00
if ( m_sMainTitle = = " " | | m_sMusicFile = = " " )
{
LOG - > Warn ( " Main title or music file for '%s' came up blank, forced to fall back on TidyUpData to fix title and paths. Do not use # or ; in a song title. " , m_sSongDir . c_str ( ) ) ;
// Tell TidyUpData that it's not loaded from the cache because it needs
// to hit the song folder to find the files that weren't found. -Kyz
TidyUpData ( false , false ) ;
}
2012-07-01 12:24:39 -05:00
}
else
{
// There was no entry in the cache for this song, or it was out of date.
// Let's load it from a file, then write a cache entry.
2015-02-27 10:36:17 -07:00
if ( ! NotesLoader : : LoadFromDir ( sDir , * this , BlacklistedImages , load_autosave ) )
2012-07-01 12:24:39 -05:00
{
LOG - > UserLog ( " Song " , sDir , " has no SSC, SM, SMA, DWI, BMS, or KSF files. " ) ;
vector < RString > vs ;
GetDirListing ( sDir + " *.mp3 " , vs , false , false ) ;
GetDirListing ( sDir + " *.oga " , vs , false , false ) ;
GetDirListing ( sDir + " *.ogg " , vs , false , false ) ;
bool bHasMusic = ! vs . empty ( ) ;
if ( ! bHasMusic )
{
LOG - > UserLog ( " Song " , sDir , " has no music file either. Ignoring this song directory. " ) ;
return false ;
}
// Make sure we have a future filename figured out.
vector < RString > folders ;
split ( sDir , " / " , folders ) ;
RString songName = folders [ 2 ] + " .ssc " ;
this - > m_sSongFileName = sDir + songName ;
// Continue on with a blank Song so that people can make adjustments using the editor.
}
2015-03-23 11:35:30 -06:00
// If edits are not cached, looking for them causes a substantial hit to
// loading time. -Kyz
LoadEditsFromSongDir ( sDir ) ;
2015-04-07 12:47:51 -06:00
TidyUpData ( false , true ) ;
2015-02-27 10:36:17 -07:00
// Don't save a cache file if the autosave is being loaded, because the
// cache file would contain the autosave filename. -Kyz
if ( ! load_autosave )
{
// save a cache file so we don't have to parse it all over again next time
if ( ! SaveToCacheFile ( ) )
{ sCacheFilePath = RString ( ) ; }
}
2012-07-01 12:24:39 -05:00
}
FOREACH ( Steps * , m_vpSteps , s )
{
/* Compress all Steps. During initial caching, this will remove cached
* NoteData; during cached loads, this will just remove cached SMData. */
( * s ) - > Compress ( ) ;
}
// Load the cached banners, if it's not loaded already.
if ( PREFSMAN - > m_BannerCache = = BNCACHE_LOW_RES_PRELOAD & & m_bHasBanner )
BANNERCACHE - > LoadBanner ( GetBannerPath ( ) ) ;
// Load the cached background, if it's not loaded already.
/*
if( PREFSMAN->m_BackgroundCache == BGCACHE_LOW_RES_PRELOAD && m_bHasBackground )
BACKGROUNDCACHE->LoadBackground( GetBackgroundPath() );
*/
// Add AutoGen pointers. (These aren't cached.)
AddAutoGenNotes ( ) ;
if ( ! m_bHasMusic )
{
LOG - > UserLog ( " Song " , sDir , " has no music; ignored. " ) ;
return false ; // don't load this song
}
return true ; // do load this song
}
2013-08-13 11:43:31 -04:00
/* This function feels EXTREMELY hacky - copying things on top of pointers so
* they don't break elsewhere. Maybe it could be rewritten to politely ask the
* Song/Steps objects to reload themselves. -- djpohly */
2012-07-01 12:24:39 -05:00
bool Song : : ReloadFromSongDir ( RString sDir )
{
2015-03-30 17:45:52 -06:00
// Remove the cache file to force the song to reload from its dir instead
// of loading from the cache. -Kyz
FILEMAN - > Remove ( GetCacheFilePath ( ) ) ;
2012-07-01 12:24:39 -05:00
RemoveAutoGenNotes ( ) ;
vector < Steps * > vOldSteps = m_vpSteps ;
Song copy ;
if ( ! copy . LoadFromSongDir ( sDir ) )
return false ;
copy . RemoveAutoGenNotes ( ) ;
* this = copy ;
2013-08-13 11:43:31 -04:00
/* Go through the steps, first setting their Song pointer to this song
* (instead of the copy used above), and constructing a map to let us
* easily find the new steps. */
2012-07-01 12:24:39 -05:00
map < StepsID , Steps * > mNewSteps ;
for ( vector < Steps * > : : const_iterator it = m_vpSteps . begin ( ) ; it ! = m_vpSteps . end ( ) ; + + it )
{
2013-08-13 11:43:31 -04:00
( * it ) - > m_pSong = this ;
2012-07-01 12:24:39 -05:00
StepsID id ;
id . FromSteps ( * it ) ;
mNewSteps [ id ] = * it ;
}
// Now we wipe out the new pointers, which were shallow copied and not deep copied...
m_vpSteps . clear ( ) ;
FOREACH_ENUM ( StepsType , i )
m_vpStepsByType [ i ] . clear ( ) ;
/* Then we copy as many Steps as possible on top of the old pointers.
* The only pointers that change are pointers to Steps that are not in the
* reverted file, which we delete, and pointers to Steps that are in the
* reverted file but not the original *this, which we create new copies of.
* We have to go through these hoops because many places assume the Steps
* pointers don't change - even though there are other ways they can change,
* such as deleting a Steps via the editor. */
for ( vector < Steps * > : : const_iterator itOld = vOldSteps . begin ( ) ; itOld ! = vOldSteps . end ( ) ; + + itOld )
{
StepsID id ;
id . FromSteps ( * itOld ) ;
map < StepsID , Steps * > : : iterator itNew = mNewSteps . find ( id ) ;
if ( itNew = = mNewSteps . end ( ) )
{
// This stepchart didn't exist in the file we reverted from
delete * itOld ;
}
else
{
Steps * OldSteps = * itOld ;
* OldSteps = * ( itNew - > second ) ;
AddSteps ( OldSteps ) ;
mNewSteps . erase ( itNew ) ;
}
}
// The leftovers in the map are steps that didn't exist before we reverted
for ( map < StepsID , Steps * > : : const_iterator it = mNewSteps . begin ( ) ; it ! = mNewSteps . end ( ) ; + + it )
{
2013-01-20 19:23:28 -05:00
Steps * NewSteps = new Steps ( this ) ;
2012-07-01 12:24:39 -05:00
* NewSteps = * ( it - > second ) ;
AddSteps ( NewSteps ) ;
}
AddAutoGenNotes ( ) ;
2015-03-31 15:20:37 -06:00
// Reload any images associated with the song. -Kyz
vector < RString > to_reload ;
to_reload . reserve ( 7 ) ;
to_reload . push_back ( m_sBannerFile ) ;
to_reload . push_back ( m_sJacketFile ) ;
to_reload . push_back ( m_sCDFile ) ;
to_reload . push_back ( m_sDiscFile ) ;
to_reload . push_back ( m_sBackgroundFile ) ;
to_reload . push_back ( m_sCDTitleFile ) ;
to_reload . push_back ( m_sPreviewVidFile ) ;
for ( vector < RString > : : iterator file = to_reload . begin ( ) ; file ! = to_reload . end ( ) ; + + file )
{
RageTextureID id ( * file ) ;
if ( TEXTUREMAN - > IsTextureRegistered ( id ) )
{
RageTexture * tex = TEXTUREMAN - > LoadTexture ( id ) ;
if ( tex )
{
tex - > Reload ( ) ;
}
}
}
2012-07-01 12:24:39 -05:00
return true ;
}
2015-03-23 11:35:30 -06:00
void Song : : LoadEditsFromSongDir ( RString dir )
{
// Load any .edit files in the song folder.
// Doing this BEFORE setting up AutoGen just in case.
vector < RString > vs ;
GetDirListing ( dir + " *.edit " , vs , false , false ) ;
// XXX: I'm sure there's a StepMania way of doing this, but familiar with this codebase I am not.
for ( unsigned int i = 0 ; i < vs . size ( ) ; + + i )
{
// Try SSCLoader
SSCLoader ldSSC ;
if ( ldSSC . LoadEditFromFile ( dir + vs [ i ] , ProfileSlot_Invalid , true , this ) ! = true )
{
// No dice? Try SMLoader then. If SMLoader fails too, well whatever.
// We don't have to do anything to fail gracefully.
SMLoader ldSM ;
ldSM . LoadEditFromFile ( dir + vs [ i ] , ProfileSlot_Invalid , true , this ) ;
}
}
// Note: If vs.empty() then this loop is skipped entirely (vs.size() == 0)
}
2015-02-27 10:36:17 -07:00
bool Song : : HasAutosaveFile ( )
{
if ( m_sSongFileName . empty ( ) )
{
return false ;
}
RString autosave_path = SetExtension ( m_sSongFileName , " ats " ) ;
return FILEMAN - > DoesFileExist ( autosave_path ) ;
}
bool Song : : LoadAutosaveFile ( )
{
if ( m_sSongFileName . empty ( ) )
{
return false ;
}
// Save these strings because they need to be restored after the reset.
// The filenames need to point to the original instead of the autosave for
// things like load from disk to work. -Kyz
RString dir = GetSongDir ( ) ;
RString song_timing_file = m_SongTiming . m_sFile ;
RString song_file = m_sSongFileName ;
// Reset needs to be used to remove all the steps and other things that
// will be loaded from the autosave. -Kyz
Reset ( ) ;
if ( LoadFromSongDir ( dir , true ) )
{
m_loaded_from_autosave = true ;
m_sSongFileName = song_file ;
m_SongTiming . m_sFile = song_timing_file ;
return true ;
}
// Loading the autosave failed, reload the original. -Kyz
LoadFromSongDir ( dir , false ) ;
return false ;
}
2012-07-01 12:24:39 -05:00
/* Fix up song paths. If there's a leading "./", be sure to keep it: it's
* a signal that the path is from the root directory, not the song directory.
* Other than a leading "./", song paths must never contain "." or "..". */
void FixupPath ( RString & path , const RString & sSongPath )
{
// Replace backslashes with slashes in all paths.
FixSlashesInPlace ( path ) ;
/* Many imported files contain erroneous whitespace before or after
* filenames. Paths usually don't actually start or end with spaces,
* so let's just remove it. */
Trim ( path ) ;
}
// Songs in BlacklistImages will never be autodetected as song images.
2015-03-23 11:35:30 -06:00
void Song : : TidyUpData ( bool from_cache , bool /* duringCache */ )
2012-07-01 12:24:39 -05:00
{
// We need to do this before calling any of HasMusic, HasHasCDTitle, etc.
2015-03-24 12:05:41 -06:00
ASSERT_M ( m_sSongDir . Left ( 3 ) ! = " ../ " , m_sSongDir ) ; // meaningless
FixupPath ( m_sSongDir , " " ) ;
FixupPath ( m_sMusicFile , m_sSongDir ) ;
FOREACH_ENUM ( InstrumentTrack , i )
{ if ( ! m_sInstrumentTrackFile [ i ] . empty ( ) )
{ FixupPath ( m_sInstrumentTrackFile [ i ] , m_sSongDir ) ; } }
FixupPath ( m_sBannerFile , m_sSongDir ) ;
FixupPath ( m_sJacketFile , m_sSongDir ) ;
FixupPath ( m_sCDFile , m_sSongDir ) ;
FixupPath ( m_sDiscFile , m_sSongDir ) ;
FixupPath ( m_sLyricsFile , m_sSongDir ) ;
FixupPath ( m_sBackgroundFile , m_sSongDir ) ;
FixupPath ( m_sCDTitleFile , m_sSongDir ) ;
2015-03-23 11:35:30 -06:00
CHECKPOINT_M ( " Looking for images... " ) ;
m_SongTiming . TidyUpData ( false ) ;
2012-07-01 12:24:39 -05:00
2015-03-23 11:35:30 -06:00
FOREACH ( Steps * , m_vpSteps , s )
2015-03-04 18:54:57 -07:00
{
2015-03-23 11:35:30 -06:00
( * s ) - > m_Timing . TidyUpData ( true ) ;
2015-03-04 18:54:57 -07:00
}
2015-03-23 11:35:30 -06:00
if ( ! from_cache )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
if ( this - > m_sArtist = = " The Dancing Monkeys Project " & & this - > m_sMainTitle . find_first_of ( ' - ' ) ! = string : : npos )
{
// Dancing Monkeys had a bug/feature where the artist was replaced. Restore it.
vector < RString > titleParts ;
split ( this - > m_sMainTitle , " - " , titleParts ) ;
this - > m_sArtist = titleParts . front ( ) ;
Trim ( this - > m_sArtist ) ;
titleParts . erase ( titleParts . begin ( ) ) ;
this - > m_sMainTitle = join ( " - " , titleParts ) ;
Trim ( this - > m_sMainTitle ) ;
}
Trim ( m_sMainTitle ) ;
Trim ( m_sSubTitle ) ;
Trim ( m_sArtist ) ;
// Fall back on the song directory name.
if ( m_sMainTitle = = " " )
{
NotesLoader : : GetMainAndSubTitlesFromFullTitle (
Basename ( this - > GetSongDir ( ) ) , m_sMainTitle , m_sSubTitle ) ;
}
if ( m_sArtist = = " " )
{ m_sArtist = " Unknown artist " ; }
TranslateTitles ( ) ;
2015-03-23 11:35:30 -06:00
// Set the has flags before tidying so that tidying can check them instead
// of using the has functions that hit the disk. -Kyz
// These will be written to cache, for Song::LoadFromSongDir to use later.
m_bHasMusic = HasMusic ( ) ;
m_bHasBanner = HasBanner ( ) ;
m_bHasBackground = HasBackground ( ) ;
if ( m_bHasBanner )
{ BANNERCACHE - > CacheBanner ( GetBannerPath ( ) ) ; }
/*
if(m_bHasBackground)
{ BANNERCACHE->CacheBackground(GetBackgroundPath()); }
*/
2015-03-24 12:05:41 -06:00
// There are several things that need to find a file from the dir with a
// particular extension or type of extension. So fetch a list of all
// files in the dir once, then split that list into the different things
// we need. -Kyz
vector < RString > song_dir_listing ;
FILEMAN - > GetDirListing ( m_sSongDir + " * " , song_dir_listing , false , false ) ;
vector < RString > music_list ;
vector < RString > image_list ;
vector < RString > movie_list ;
vector < RString > lyric_list ;
vector < RString > lyric_extensions ( 1 , " lrc " ) ;
// Using a pair didn't work, so these two vectors have to be kept in
// sync instead. -Kyz
vector < vector < RString > * > lists_to_fill ;
vector < const vector < RString > * > fill_exts ;
lists_to_fill . reserve ( 4 ) ;
fill_exts . reserve ( 4 ) ;
lists_to_fill . push_back ( & music_list ) ;
fill_exts . push_back ( & ActorUtil : : GetTypeExtensionList ( FT_Sound ) ) ;
lists_to_fill . push_back ( & image_list ) ;
fill_exts . push_back ( & ActorUtil : : GetTypeExtensionList ( FT_Bitmap ) ) ;
lists_to_fill . push_back ( & movie_list ) ;
fill_exts . push_back ( & ActorUtil : : GetTypeExtensionList ( FT_Movie ) ) ;
lists_to_fill . push_back ( & lyric_list ) ;
fill_exts . push_back ( & lyric_extensions ) ;
for ( vector < RString > : : iterator filename = song_dir_listing . begin ( ) ;
filename ! = song_dir_listing . end ( ) ; + + filename )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
bool matched_something = false ;
RString file_ext = GetExtension ( * filename ) . MakeLower ( ) ;
if ( ! file_ext . empty ( ) )
{
for ( size_t tf = 0 ; tf < lists_to_fill . size ( ) ; + + tf )
{
for ( vector < RString > : : const_iterator ext = fill_exts [ tf ] - > begin ( ) ;
ext ! = fill_exts [ tf ] - > end ( ) ; + + ext )
{
if ( file_ext = = * ext )
{
lists_to_fill [ tf ] - > push_back ( * filename ) ;
matched_something = true ;
break ;
}
}
if ( matched_something )
{
break ;
}
}
}
}
2012-07-01 12:24:39 -05:00
2015-03-24 12:05:41 -06:00
if ( ! m_bHasMusic )
{
// If the first song is "intro", and we have more than one available,
// don't use it--it's probably a KSF intro music file, which we don't
// (yet) support.
if ( ! music_list . empty ( ) )
2015-03-23 11:35:30 -06:00
{
2015-03-24 12:05:41 -06:00
LOG - > Trace ( " Song '%s' points to a music file that doesn't exist, found music file '%s' " , m_sSongDir . c_str ( ) , music_list [ 0 ] . c_str ( ) ) ;
2015-03-23 11:35:30 -06:00
m_bHasMusic = true ;
2015-03-24 12:05:41 -06:00
m_sMusicFile = music_list [ 0 ] ;
if ( music_list . size ( ) > 1 & &
! m_sMusicFile . Left ( 5 ) . CompareNoCase ( " intro " ) )
{
m_sMusicFile = music_list [ 1 ] ;
}
2015-03-23 11:35:30 -06:00
}
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
// This must be done before radar calculation.
if ( m_bHasMusic )
2012-07-01 12:24:39 -05:00
{
2015-03-23 11:35:30 -06:00
RString error ;
RageSoundReader * Sample = RageSoundReader_FileReader : : OpenFile ( GetMusicPath ( ) , error ) ;
/* XXX: Checking if the music file exists eliminates a warning
* originating from BMS files (which have no music file, per se)
* but it's something of a hack. */
if ( Sample = = NULL & & m_sMusicFile ! = " " )
2012-07-01 12:24:39 -05:00
{
2015-03-23 11:35:30 -06:00
LOG - > UserLog ( " Sound file " , GetMusicPath ( ) , " couldn't be opened: %s " , error . c_str ( ) ) ;
// Don't use this file.
m_sMusicFile = " " ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
else if ( Sample ! = NULL )
2012-07-01 12:24:39 -05:00
{
2015-03-23 11:35:30 -06:00
m_fMusicLengthSeconds = Sample - > GetLength ( ) / 1000.0f ;
delete Sample ;
if ( m_fMusicLengthSeconds < 0 )
{
// It failed; bad file or something. It's already logged a warning.
m_fMusicLengthSeconds = 100 ; // guess
}
else if ( m_fMusicLengthSeconds = = 0 )
{
LOG - > UserLog ( " Sound file " , GetMusicPath ( ) , " is empty. " ) ;
}
2012-07-01 12:24:39 -05:00
}
}
2015-03-23 11:35:30 -06:00
else // ! HasMusic()
2012-07-01 12:24:39 -05:00
{
2015-03-23 11:35:30 -06:00
m_fMusicLengthSeconds = 100 ; // guess
LOG - > UserLog ( " Song " ,
GetSongDir ( ) ,
" has no music file; guessing at %f seconds " ,
m_fMusicLengthSeconds ) ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
if ( m_fMusicLengthSeconds < 0 )
{
LOG - > UserLog ( " Sound file " ,
GetMusicPath ( ) ,
" has a negative length %f. " ,
m_fMusicLengthSeconds ) ;
m_fMusicLengthSeconds = 0 ;
}
if ( m_fMusicSampleStartSeconds = = - 1 | |
m_fMusicSampleLengthSeconds = = 0 | |
m_fMusicSampleStartSeconds + m_fMusicSampleLengthSeconds > this - > m_fMusicLengthSeconds )
{
const TimingData & timing = this - > m_SongTiming ;
m_fMusicSampleStartSeconds = timing . GetElapsedTimeFromBeat ( 100 ) ;
2012-07-01 12:24:39 -05:00
2015-03-23 11:35:30 -06:00
if ( m_fMusicSampleStartSeconds + m_fMusicSampleLengthSeconds > this - > m_fMusicLengthSeconds )
{
// Attempt to get a reasonable default.
int iBeat = lrintf ( this - > m_SongTiming . GetBeatFromElapsedTime ( this - > GetLastSecond ( ) ) / 2 ) ;
iBeat - = iBeat % 4 ;
m_fMusicSampleStartSeconds = timing . GetElapsedTimeFromBeat ( ( float ) iBeat ) ;
}
}
2012-07-01 12:24:39 -05:00
2015-03-23 11:35:30 -06:00
// The old logic meant that you couldn't have sample lengths that go forever,
// e.g. those in Donkey Konga. I never liked that. -freem
if ( m_fMusicSampleLengthSeconds < = 0.00f )
{ m_fMusicSampleLengthSeconds = DEFAULT_MUSIC_SAMPLE_LENGTH ; }
2012-07-01 12:24:39 -05:00
2015-03-24 12:05:41 -06:00
// Here's the problem: We have a directory full of images. We want to
// determine which image is the banner, which is the background, and
// which is the CDTitle.
2015-03-23 11:35:30 -06:00
// For blank args to FindFirstFilenameContaining. -Kyz
vector < RString > empty_list ;
2012-07-01 12:24:39 -05:00
2015-03-23 11:35:30 -06:00
bool has_jacket = HasJacket ( ) ;
bool has_cdimage = HasCDImage ( ) ;
bool has_disc = HasDisc ( ) ;
bool has_cdtitle = HasCDTitle ( ) ;
2012-07-01 12:24:39 -05:00
// First, check the file name for hints.
2015-03-23 11:35:30 -06:00
if ( ! m_bHasBanner )
2012-07-01 12:24:39 -05:00
{
/* If a nonexistant banner file is specified, and we can't find a
* replacement, don't wipe out the old value. */
//m_sBannerFile = "";
// find an image with "banner" in the file name
2015-03-23 11:35:30 -06:00
vector < RString > contains ( 1 , " banner " ) ;
2012-07-01 12:24:39 -05:00
/* Some people do things differently for the sake of being different.
* Don't match eg. abnormal, numbness. */
2015-03-23 11:35:30 -06:00
vector < RString > ends_with ( 1 , " bn " ) ;
2015-03-24 12:05:41 -06:00
m_bHasBanner = FindFirstFilenameContaining ( image_list ,
2015-03-23 11:35:30 -06:00
m_sBannerFile , empty_list , contains , ends_with ) ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
if ( ! m_bHasBackground )
2012-07-01 12:24:39 -05:00
{
//m_sBackgroundFile = "";
// find an image with "bg" or "background" in the file name
2015-03-23 11:35:30 -06:00
vector < RString > contains ( 1 , " background " ) ;
vector < RString > ends_with ( 1 , " bg " ) ;
2015-03-24 12:05:41 -06:00
m_bHasBackground = FindFirstFilenameContaining ( image_list ,
2015-03-23 11:35:30 -06:00
m_sBackgroundFile , empty_list , contains , ends_with ) ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
if ( ! has_jacket )
2012-07-01 12:24:39 -05:00
{
// find an image with "jacket" or "albumart" in the filename.
2015-03-23 11:35:30 -06:00
vector < RString > starts_with ( 1 , " jk_ " ) ;
vector < RString > contains ;
contains . reserve ( 2 ) ;
contains . push_back ( " jacket " ) ;
contains . push_back ( " albumart " ) ;
2015-03-24 12:05:41 -06:00
has_jacket = FindFirstFilenameContaining ( image_list ,
2015-03-23 11:35:30 -06:00
m_sJacketFile , starts_with , contains , empty_list ) ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
if ( ! has_cdimage )
2012-07-01 12:24:39 -05:00
{
// CD image, a la ddr 1st-3rd (not to be confused with CDTitles)
// find an image with "-cd" at the end of the filename.
2015-03-23 11:35:30 -06:00
vector < RString > ends_with ( 1 , " -cd " ) ;
2015-03-24 12:05:41 -06:00
has_cdimage = FindFirstFilenameContaining ( image_list ,
2015-03-23 11:35:30 -06:00
m_sCDFile , empty_list , empty_list , ends_with ) ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
if ( ! has_disc )
2012-07-01 12:24:39 -05:00
{
// a rectangular graphic, not to be confused with CDImage above.
2015-03-23 11:35:30 -06:00
vector < RString > ends_with ;
ends_with . reserve ( 2 ) ;
ends_with . push_back ( " disc " ) ;
ends_with . push_back ( " title " ) ;
2015-03-24 12:05:41 -06:00
has_disc = FindFirstFilenameContaining ( image_list ,
2015-03-23 11:35:30 -06:00
m_sDiscFile , empty_list , empty_list , ends_with ) ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
if ( ! has_cdtitle )
2012-07-01 12:24:39 -05:00
{
// find an image with "cdtitle" in the file name
2015-03-23 11:35:30 -06:00
vector < RString > contains ( 1 , " cdtitle " ) ;
2015-03-24 12:05:41 -06:00
has_cdtitle = FindFirstFilenameContaining ( image_list ,
2015-03-23 11:35:30 -06:00
m_sCDTitleFile , empty_list , contains , empty_list ) ;
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
if ( ! HasLyrics ( ) )
2012-07-01 12:24:39 -05:00
{
// Check if there is a lyric file in here
2015-03-24 12:05:41 -06:00
if ( ! lyric_list . empty ( ) )
{
m_sLyricsFile = lyric_list [ 0 ] ;
}
2012-07-01 12:24:39 -05:00
}
/* Now, For the images we still haven't found,
* look at the image dimensions of the remaining unclassified images. */
2015-03-24 12:05:41 -06:00
for ( unsigned int i = 0 ; i < image_list . size ( ) ; + + i ) // foreach image
2012-07-01 12:24:39 -05:00
{
2015-03-23 11:35:30 -06:00
if ( m_bHasBanner & & m_bHasBackground & & has_cdtitle )
2012-07-01 12:24:39 -05:00
break ; // done
// ignore DWI "-char" graphics
2015-03-24 12:05:41 -06:00
RString lower = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
lower . MakeLower ( ) ;
if ( BlacklistedImages . find ( lower ) ! = BlacklistedImages . end ( ) )
2012-07-01 12:24:39 -05:00
continue ; // skip
// Skip any image that we've already classified
2015-03-24 12:05:41 -06:00
if ( m_bHasBanner & & m_sBannerFile . EqualsNoCase ( image_list [ i ] ) )
2012-07-01 12:24:39 -05:00
continue ; // skip
2015-03-24 12:05:41 -06:00
if ( m_bHasBackground & & m_sBackgroundFile . EqualsNoCase ( image_list [ i ] ) )
2012-07-01 12:24:39 -05:00
continue ; // skip
2015-03-24 12:05:41 -06:00
if ( has_cdtitle & & m_sCDTitleFile . EqualsNoCase ( image_list [ i ] ) )
2012-07-01 12:24:39 -05:00
continue ; // skip
2015-03-24 12:05:41 -06:00
if ( has_jacket & & m_sJacketFile . EqualsNoCase ( image_list [ i ] ) )
2012-07-01 12:24:39 -05:00
continue ; // skip
2015-03-24 12:05:41 -06:00
if ( has_disc & & m_sDiscFile . EqualsNoCase ( image_list [ i ] ) )
2012-07-01 12:24:39 -05:00
continue ; // skip
2015-03-24 12:05:41 -06:00
if ( has_cdimage & & m_sCDFile . EqualsNoCase ( image_list [ i ] ) )
2012-07-01 12:24:39 -05:00
continue ; // skip
2015-03-24 12:05:41 -06:00
RString sPath = m_sSongDir + image_list [ i ] ;
2012-07-01 12:24:39 -05:00
// We only care about the dimensions.
RString error ;
2015-03-23 11:35:30 -06:00
RageSurface * img = RageSurfaceUtils : : LoadFile ( sPath , error , true ) ;
if ( ! img )
2012-07-01 12:24:39 -05:00
{
2015-03-23 11:35:30 -06:00
LOG - > UserLog ( " Graphic file " , sPath , " couldn't be loaded: %s " , error . c_str ( ) ) ;
2012-07-01 12:24:39 -05:00
continue ;
}
const int width = img - > w ;
const int height = img - > h ;
delete img ;
2015-03-23 11:35:30 -06:00
if ( ! m_bHasBackground & & width > = 320 & & height > = 240 )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
m_sBackgroundFile = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
m_bHasBackground = true ;
2012-07-01 12:24:39 -05:00
continue ;
}
2015-03-23 11:35:30 -06:00
if ( ! m_bHasBanner & & 100 < = width & & width < = 320 & &
50 < = height & & height < = 240 )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
m_sBannerFile = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
m_bHasBanner = true ;
2012-07-01 12:24:39 -05:00
continue ;
}
/* Some songs have overlarge banners. Check if the ratio is reasonable
* (over 2:1; usually over 3:1), and large (not a cdtitle). */
2015-03-23 11:35:30 -06:00
if ( ! m_bHasBanner & & width > 200 & & float ( width ) / height > 2.0f )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
m_sBannerFile = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
m_bHasBanner = true ;
2012-07-01 12:24:39 -05:00
continue ;
}
/* Agh. DWI's inline title images are triggering this, resulting in
* kanji, etc., being used as a CDTitle for songs with none. Some
* sample data from random incarnations:
* 42x50 35x50 50x50 144x49
* It looks like ~50 height is what people use to align to DWI's font.
*
* My tallest CDTitle is 44. Let's cut off in the middle and hope for
* the best. -(who? -aj) */
/* The proper size of a CDTitle is 64x48 or sometihng. Simfile artists
* typically don't give a shit about this (see Cetaka's fucking banner
* -sized CDTitle). This is also subverted in certain designs (beta
* Mungyodance 3 simfiles, for instance, used the CDTitle to hold
* various information about the song in question). As it stands,
* I'm keeping this code until I figure out wtf to do -aj
*/
2015-03-23 11:35:30 -06:00
if ( ! has_cdtitle & & width < = 100 & & height < = 48 )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
m_sCDTitleFile = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
has_cdtitle = true ;
2012-07-01 12:24:39 -05:00
continue ;
}
// Jacket files typically have the same width and height.
2015-03-23 11:35:30 -06:00
if ( ! has_jacket & & width = = height )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
m_sJacketFile = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
has_jacket = true ;
2012-07-01 12:24:39 -05:00
continue ;
}
// Disc images are typically rectangular; make sure we have a banner already.
2015-03-23 11:35:30 -06:00
if ( ! has_disc & & ( width > height ) & & m_bHasBanner )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
if ( image_list [ i ] ! = m_sBannerFile )
2015-03-23 11:35:30 -06:00
{
2015-03-24 12:05:41 -06:00
m_sDiscFile = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
has_disc = true ;
}
2012-07-01 12:24:39 -05:00
continue ;
}
// CD images are the same as Jackets, typically the same width and height
2015-03-23 11:35:30 -06:00
if ( ! has_cdimage & & width = = height )
2012-07-01 12:24:39 -05:00
{
2015-03-24 12:05:41 -06:00
m_sCDFile = image_list [ i ] ;
2015-03-23 11:35:30 -06:00
has_cdimage = true ;
2012-07-01 12:24:39 -05:00
continue ;
}
}
2015-03-23 11:35:30 -06:00
// If no BGChanges are specified and there are movies in the song
// directory, then assume they are DWI style where the movie begins at
// beat 0.
if ( ! HasBGChanges ( ) )
{
/* Use this->GetBeatFromElapsedTime(0) instead of 0 to start when the
* music starts. */
2015-03-24 12:05:41 -06:00
if ( movie_list . size ( ) = = 1 )
{
this - > AddBackgroundChange ( BACKGROUND_LAYER_1 ,
BackgroundChange ( 0 , movie_list [ 0 ] , " " , 1.f ,
SBE_StretchNoLoop ) ) ;
}
2015-03-23 11:35:30 -06:00
}
// Don't allow multiple Steps of the same StepsType and Difficulty
// (except for edits). We should be able to use difficulty names as
// unique identifiers for steps. */
SongUtil : : AdjustDuplicateSteps ( this ) ;
2015-04-27 07:48:50 -06:00
// Clear fields for files that turned out to not exist.
# define CLEAR_NOT_HAS(has_name, field_name) if(!has_name) { field_name= ""; }
CLEAR_NOT_HAS ( m_bHasBanner , m_sBannerFile ) ;
CLEAR_NOT_HAS ( m_bHasBackground , m_sBackgroundFile ) ;
CLEAR_NOT_HAS ( has_jacket , m_sJacketFile ) ;
CLEAR_NOT_HAS ( has_cdimage , m_sCDFile ) ;
CLEAR_NOT_HAS ( has_disc , m_sDiscFile ) ;
CLEAR_NOT_HAS ( has_cdtitle , m_sCDTitleFile ) ;
# undef CLEAR_NOT_HAS
2012-07-01 12:24:39 -05:00
}
2015-03-23 11:35:30 -06:00
/* Generate these before we autogen notes, so the new notes can inherit
* their source's values. */
ReCalculateRadarValuesAndLastSecond ( from_cache , true ) ;
// If the music length is suspiciously shorter than the last second, adjust
// the length. This prevents the ogg patch from setting a false length. -Kyz
if ( m_fMusicLengthSeconds < lastSecond - 10.0f )
2012-07-01 12:24:39 -05:00
{
2015-03-23 11:35:30 -06:00
m_fMusicLengthSeconds = lastSecond ;
2012-07-01 12:24:39 -05:00
}
}
void Song : : TranslateTitles ( )
{
static TitleSubst tsub ( " Songs " ) ;
TitleFields title ;
title . LoadFromStrings ( m_sMainTitle , m_sSubTitle , m_sArtist ,
m_sMainTitleTranslit , m_sSubTitleTranslit , m_sArtistTranslit ) ;
tsub . Subst ( title ) ;
title . SaveToStrings ( m_sMainTitle , m_sSubTitle , m_sArtist ,
m_sMainTitleTranslit , m_sSubTitleTranslit , m_sArtistTranslit ) ;
}
void Song : : ReCalculateRadarValuesAndLastSecond ( bool fromCache , bool duringCache )
{
if ( fromCache & & this - > GetFirstSecond ( ) > = 0 & & this - > GetLastSecond ( ) > 0 )
{
// this is loaded from cache, then we just have to calculate the radar values.
for ( unsigned i = 0 ; i < m_vpSteps . size ( ) ; i + + )
m_vpSteps [ i ] - > CalculateRadarValues ( m_fMusicLengthSeconds ) ;
return ;
}
float localFirst = FLT_MAX ; // inf
// Make sure we're at least as long as the specified amount below.
float localLast = this - > specifiedLastSecond ;
for ( unsigned i = 0 ; i < m_vpSteps . size ( ) ; i + + )
{
Steps * pSteps = m_vpSteps [ i ] ;
pSteps - > CalculateRadarValues ( m_fMusicLengthSeconds ) ;
// Must initialize before the gotos.
NoteData tempNoteData ;
pSteps - > GetNoteData ( tempNoteData ) ;
// calculate lastSecond
2013-01-25 17:57:03 -05:00
/* 1. If it's autogen, then first/last beat will come from the parent.
* 2. Don't calculate with edits unless the song only contains an edit
2012-07-01 12:24:39 -05:00
* chart, like those in Mungyodance 3. Otherwise, edits installed on
* the machine could extend the length of the song. */
2013-01-25 17:57:03 -05:00
if ( ! pSteps - > IsAutogen ( ) & &
! ( pSteps - > IsAnEdit ( ) & & m_vpSteps . size ( ) > 1 ) )
2012-07-01 12:24:39 -05:00
{
2013-01-25 17:57:03 -05:00
// Don't set first/last beat based on lights. They often start very
// early and end very late.
if ( pSteps - > m_StepsType = = StepsType_lights_cabinet )
continue ; // no need to wipe this.
/* Many songs have stray, empty song patterns. Ignore them, so they
* don't force the first beat of the whole song to 0. */
if ( tempNoteData . GetLastRow ( ) ! = 0 )
{
localFirst = min ( localFirst ,
pSteps - > GetTimingData ( ) - > GetElapsedTimeFromBeat ( tempNoteData . GetFirstBeat ( ) ) ) ;
localLast = max ( localLast ,
pSteps - > GetTimingData ( ) - > GetElapsedTimeFromBeat ( tempNoteData . GetLastBeat ( ) ) ) ;
}
2012-07-01 12:24:39 -05:00
}
2013-01-25 17:57:03 -05:00
// Wipe NoteData
2012-07-01 12:24:39 -05:00
if ( duringCache )
{
NoteData dummy ;
dummy . SetNumTracks ( tempNoteData . GetNumTracks ( ) ) ;
pSteps - > SetNoteData ( dummy ) ;
}
}
// Yes, for some reason we can have freaky stuff take place here.
this - > firstSecond = ( localFirst < localLast ) ? localFirst : 0 ;
this - > lastSecond = localLast ;
}
// Return whether the song is playable in the given style.
bool Song : : SongCompleteForStyle ( const Style * st ) const
{
return HasStepsType ( st - > m_StepsType ) ;
}
bool Song : : HasStepsType ( StepsType st ) const
{
return SongUtil : : GetOneSteps ( this , st ) ! = NULL ;
}
bool Song : : HasStepsTypeAndDifficulty ( StepsType st , Difficulty dc ) const
{
return SongUtil : : GetOneSteps ( this , st , dc ) ! = NULL ;
}
2015-02-17 15:37:49 -07:00
void Song : : Save ( bool autosave )
2012-07-01 12:24:39 -05:00
{
LOG - > Trace ( " Song::SaveToSongFile() " ) ;
ReCalculateRadarValuesAndLastSecond ( ) ;
TranslateTitles ( ) ;
// Save the new files. These calls make backups on their own.
2015-02-17 15:37:49 -07:00
if ( ! SaveToSSCFile ( GetSongFilePath ( ) , false , autosave ) )
2012-07-01 12:24:39 -05:00
return ;
2015-02-27 10:36:17 -07:00
// Skip saving the cache, sm, and .old files if we are autosaving. The
// cache file should not contain the autosave filename. -Kyz
2015-02-17 15:37:49 -07:00
if ( autosave )
{
return ;
}
2015-02-27 10:36:17 -07:00
SaveToCacheFile ( ) ;
2012-07-01 12:24:39 -05:00
SaveToSMFile ( ) ;
//SaveToDWIFile();
/* We've safely written our files and created backups. Rename non-SM and
* non-DWI files to avoid confusion. */
vector < RString > arrayOldFileNames ;
GetDirListing ( m_sSongDir + " *.bms " , arrayOldFileNames ) ;
GetDirListing ( m_sSongDir + " *.pms " , arrayOldFileNames ) ;
GetDirListing ( m_sSongDir + " *.ksf " , arrayOldFileNames ) ;
for ( unsigned i = 0 ; i < arrayOldFileNames . size ( ) ; i + + )
{
const RString sOldPath = m_sSongDir + arrayOldFileNames [ i ] ;
const RString sNewPath = sOldPath + " .old " ;
if ( ! FileCopy ( sOldPath , sNewPath ) )
{
LOG - > UserLog ( " Song file " , sOldPath , " couldn't be backed up. " ) ;
// Don't remove.
}
else
FILEMAN - > Remove ( sOldPath ) ;
}
}
bool Song : : SaveToSMFile ( )
{
const RString sPath = SetExtension ( GetSongFilePath ( ) , " sm " ) ;
LOG - > Trace ( " Song::SaveToSMFile(%s) " , sPath . c_str ( ) ) ;
// If the file exists, make a backup.
if ( IsAFile ( sPath ) )
FileCopy ( sPath , sPath + " .old " ) ;
vector < Steps * > vpStepsToSave ;
FOREACH_CONST ( Steps * , m_vpSteps , s )
{
Steps * pSteps = * s ;
if ( pSteps - > IsAutogen ( ) )
continue ; // don't write autogen notes
// Only save steps that weren't loaded from a profile.
if ( pSteps - > WasLoadedFromProfile ( ) )
continue ;
vpStepsToSave . push_back ( pSteps ) ;
}
2014-10-20 05:14:54 -06:00
FOREACH_CONST ( Steps * , m_UnknownStyleSteps , s )
{
vpStepsToSave . push_back ( * s ) ;
}
2012-07-01 12:24:39 -05:00
return NotesWriterSM : : Write ( sPath , * this , vpStepsToSave ) ;
}
2015-02-17 15:37:49 -07:00
bool Song : : SaveToSSCFile ( RString sPath , bool bSavingCache , bool autosave )
2012-07-01 12:24:39 -05:00
{
RString path = sPath ;
if ( ! bSavingCache )
path = SetExtension ( sPath , " ssc " ) ;
2015-02-17 15:37:49 -07:00
if ( autosave )
{
path = SetExtension ( sPath , " ats " ) ;
}
2012-07-01 12:24:39 -05:00
LOG - > Trace ( " Song::SaveToSSCFile('%s') " , path . c_str ( ) ) ;
// If the file exists, make a backup.
2015-02-17 15:37:49 -07:00
if ( ! bSavingCache & & ! autosave & & IsAFile ( path ) )
2012-07-01 12:24:39 -05:00
FileCopy ( path , path + " .old " ) ;
vector < Steps * > vpStepsToSave ;
FOREACH_CONST ( Steps * , m_vpSteps , s )
{
Steps * pSteps = * s ;
if ( pSteps - > IsAutogen ( ) )
continue ; // don't write autogen notes
// Only save steps that weren't loaded from a profile.
if ( pSteps - > WasLoadedFromProfile ( ) )
continue ;
if ( ! bSavingCache )
pSteps - > SetFilename ( path ) ;
vpStepsToSave . push_back ( pSteps ) ;
}
2014-10-20 05:14:54 -06:00
FOREACH_CONST ( Steps * , m_UnknownStyleSteps , s )
{
vpStepsToSave . push_back ( * s ) ;
}
2012-07-01 12:24:39 -05:00
2015-02-17 15:37:49 -07:00
if ( bSavingCache | | autosave )
2012-07-01 12:24:39 -05:00
{
return NotesWriterSSC : : Write ( path , * this , vpStepsToSave , bSavingCache ) ;
}
if ( ! NotesWriterSSC : : Write ( path , * this , vpStepsToSave , bSavingCache ) )
return false ;
2015-02-27 10:36:17 -07:00
RemoveAutosave ( ) ;
2015-02-17 15:37:49 -07:00
2012-07-01 12:24:39 -05:00
if ( g_BackUpAllSongSaves . Get ( ) )
{
RString sExt = GetExtension ( path ) ;
RString sBackupFile = SetExtension ( path , " " ) ;
time_t cur_time ;
time ( & cur_time ) ;
struct tm now ;
localtime_r ( & cur_time , & now ) ;
sBackupFile + = ssprintf ( " -%04i-%02i-%02i--%02i-%02i-%02i " ,
1900 + now . tm_year , now . tm_mon + 1 , now . tm_mday , now . tm_hour , now . tm_min , now . tm_sec ) ;
sBackupFile = SetExtension ( sBackupFile , sExt ) ;
sBackupFile + = ssprintf ( " .old " ) ;
if ( FileCopy ( path , sBackupFile ) )
LOG - > Trace ( " Backed up %s to %s " , path . c_str ( ) , sBackupFile . c_str ( ) ) ;
else
LOG - > Trace ( " Failed to back up %s to %s " , path . c_str ( ) , sBackupFile . c_str ( ) ) ;
}
// Mark these steps saved to disk.
FOREACH ( Steps * , vpStepsToSave , s )
( * s ) - > SetSavedToDisk ( true ) ;
return true ;
}
2012-07-01 12:30:14 -05:00
bool Song : : SaveToJsonFile ( RString sPath )
2012-07-01 12:24:39 -05:00
{
LOG - > Trace ( " Song::SaveToJsonFile('%s') " , sPath . c_str ( ) ) ;
return NotesWriterJson : : WriteSong ( sPath , * this , true ) ;
}
bool Song : : SaveToCacheFile ( )
{
2015-03-31 15:20:37 -06:00
if ( SONGMAN - > IsGroupNeverCached ( m_sGroupName ) )
{
return true ;
}
2012-07-01 12:24:39 -05:00
SONGINDEX - > AddCacheIndex ( m_sSongDir , GetHashForDirectory ( m_sSongDir ) ) ;
const RString sPath = GetCacheFilePath ( ) ;
return SaveToSSCFile ( sPath , true ) ;
}
bool Song : : SaveToDWIFile ( )
{
const RString sPath = SetExtension ( GetSongFilePath ( ) , " dwi " ) ;
LOG - > Trace ( " Song::SaveToDWIFile(%s) " , sPath . c_str ( ) ) ;
// If the file exists, make a backup.
if ( IsAFile ( sPath ) )
FileCopy ( sPath , sPath + " .old " ) ;
return NotesWriterDWI : : Write ( sPath , * this ) ;
}
2015-02-27 10:36:17 -07:00
void Song : : RemoveAutosave ( )
{
RString autosave_path = SetExtension ( m_sSongFileName , " ats " ) ;
if ( FILEMAN - > DoesFileExist ( autosave_path ) )
{
// Change all the steps to point to the actual file, not the autosave
// file. -Kyz
RString extension = GetExtension ( m_sSongFileName ) ;
for ( size_t i = 0 ; i < m_vpSteps . size ( ) ; + + i )
{
if ( ! m_vpSteps [ i ] - > IsAutogen ( ) )
{
m_vpSteps [ i ] - > SetFilename (
SetExtension ( m_vpSteps [ i ] - > GetFilename ( ) , extension ) ) ;
}
}
FILEMAN - > Remove ( autosave_path ) ;
m_loaded_from_autosave = false ;
}
}
2012-07-01 12:24:39 -05:00
void Song : : AddAutoGenNotes ( )
{
bool HasNotes [ NUM_StepsType ] ;
memset ( HasNotes , 0 , sizeof ( HasNotes ) ) ;
for ( unsigned i = 0 ; i < m_vpSteps . size ( ) ; i + + ) // foreach Steps
{
if ( m_vpSteps [ i ] - > IsAutogen ( ) )
continue ;
StepsType st = m_vpSteps [ i ] - > m_StepsType ;
HasNotes [ st ] = true ;
}
FOREACH_ENUM ( StepsType , stMissing )
{
if ( HasNotes [ stMissing ] )
continue ;
// If m_bAutogenSteps is disabled, only autogen lights.
if ( ! PREFSMAN - > m_bAutogenSteps & & stMissing ! = StepsType_lights_cabinet )
continue ;
if ( ! GAMEMAN - > GetStepsTypeInfo ( stMissing ) . bAllowAutogen )
continue ;
// missing Steps of this type
int iNumTracksOfMissing = GAMEMAN - > GetStepsTypeInfo ( stMissing ) . iNumTracks ;
// look for closest match
StepsType stBestMatch = StepsType_Invalid ;
int iBestTrackDifference = INT_MAX ;
FOREACH_ENUM ( StepsType , st )
{
if ( ! HasNotes [ st ] )
continue ;
// has (non-autogen) Steps of this type
const int iNumTracks = GAMEMAN - > GetStepsTypeInfo ( st ) . iNumTracks ;
const int iTrackDifference = abs ( iNumTracks - iNumTracksOfMissing ) ;
if ( iTrackDifference < iBestTrackDifference )
{
stBestMatch = st ;
iBestTrackDifference = iTrackDifference ;
}
}
if ( stBestMatch ! = StepsType_Invalid )
AutoGen ( stMissing , stBestMatch ) ;
}
}
void Song : : AutoGen ( StepsType ntTo , StepsType ntFrom )
{
// int iNumTracksOfTo = GAMEMAN->StepsTypeToNumTracks(ntTo);
for ( unsigned int j = 0 ; j < m_vpSteps . size ( ) ; j + + )
{
const Steps * pOriginalNotes = m_vpSteps [ j ] ;
if ( pOriginalNotes - > m_StepsType = = ntFrom )
{
2013-01-20 19:23:28 -05:00
Steps * pNewNotes = new Steps ( this ) ;
2012-07-01 12:24:39 -05:00
pNewNotes - > AutogenFrom ( pOriginalNotes , ntTo ) ;
this - > AddSteps ( pNewNotes ) ;
}
}
}
void Song : : RemoveAutoGenNotes ( )
{
FOREACH_ENUM ( StepsType , st )
{
for ( int j = m_vpStepsByType [ st ] . size ( ) - 1 ; j > = 0 ; j - - )
{
if ( m_vpStepsByType [ st ] [ j ] - > IsAutogen ( ) )
{
// delete m_vpSteps[j]; // delete below
m_vpStepsByType [ st ] . erase ( m_vpStepsByType [ st ] . begin ( ) + j ) ;
}
}
}
for ( int j = m_vpSteps . size ( ) - 1 ; j > = 0 ; j - - )
{
if ( m_vpSteps [ j ] - > IsAutogen ( ) )
{
delete m_vpSteps [ j ] ;
m_vpSteps . erase ( m_vpSteps . begin ( ) + j ) ;
}
}
}
bool Song : : IsEasy ( StepsType st ) const
{
/* Very fast songs and songs with wide tempo changes are hard for new
* players, even if they have beginner steps. */
DisplayBpms bpms ;
this - > GetDisplayBpms ( bpms ) ;
if ( bpms . GetMax ( ) > = 250 | | bpms . GetMax ( ) - bpms . GetMin ( ) > = 75 )
return false ;
/* The easy marker indicates which songs a beginner, having selected
* "beginner", can play and actually get a very easy song: if there are
* actual beginner steps, or if the light steps are 1- or 2-foot. */
const Steps * pBeginnerNotes = SongUtil : : GetStepsByDifficulty ( this , st , Difficulty_Beginner ) ;
if ( pBeginnerNotes )
return true ;
const Steps * pEasyNotes = SongUtil : : GetStepsByDifficulty ( this , st , Difficulty_Easy ) ;
if ( pEasyNotes & & pEasyNotes - > GetMeter ( ) = = 1 )
return true ;
return false ;
}
bool Song : : IsTutorial ( ) const
{
// A Song is considered a Tutorial if it has only Beginner steps.
FOREACH_CONST ( Steps * , m_vpSteps , s )
{
if ( ( * s ) - > m_StepsType = = StepsType_lights_cabinet )
continue ; // ignore
if ( ( * s ) - > GetDifficulty ( ) ! = Difficulty_Beginner )
return false ;
}
return true ;
}
bool Song : : HasEdits ( StepsType st ) const
{
for ( unsigned i = 0 ; i < m_vpSteps . size ( ) ; i + + )
{
Steps * pSteps = m_vpSteps [ i ] ;
if ( pSteps - > m_StepsType = = st & &
pSteps - > GetDifficulty ( ) = = Difficulty_Edit )
{
return true ;
}
}
return false ;
}
bool Song : : NormallyDisplayed ( ) const
{
return UNLOCKMAN = = NULL | | ! UNLOCKMAN - > SongIsLocked ( this ) ;
}
bool Song : : ShowInDemonstrationAndRanking ( ) const { return ! IsTutorial ( ) & & NormallyDisplayed ( ) ; }
// Hack: see Song::TidyUpData comments.
bool Song : : HasMusic ( ) const
{
// If we have keys, we always have music.
if ( m_vsKeysoundFile . size ( ) ! = 0 )
return true ;
return m_sMusicFile ! = " " & & IsAFile ( GetMusicPath ( ) ) ;
}
bool Song : : HasBanner ( ) const
{
return m_sBannerFile ! = " " & & IsAFile ( GetBannerPath ( ) ) ;
}
bool Song : : HasInstrumentTrack ( InstrumentTrack it ) const
{
return m_sInstrumentTrackFile [ it ] ! = " " & & IsAFile ( GetInstrumentTrackPath ( it ) ) ;
}
bool Song : : HasLyrics ( ) const
{
return m_sLyricsFile ! = " " & & IsAFile ( GetLyricsPath ( ) ) ;
}
bool Song : : HasBackground ( ) const
{
return m_sBackgroundFile ! = " " & & IsAFile ( GetBackgroundPath ( ) ) ;
}
bool Song : : HasCDTitle ( ) const
{
return m_sCDTitleFile ! = " " & & IsAFile ( GetCDTitlePath ( ) ) ;
}
bool Song : : HasBGChanges ( ) const
{
FOREACH_BackgroundLayer ( i )
{
if ( ! GetBackgroundChanges ( i ) . empty ( ) )
return true ;
}
return false ;
}
bool Song : : HasAttacks ( ) const { return ! m_Attacks . empty ( ) ; }
bool Song : : HasJacket ( ) const
{
return m_sJacketFile ! = " " & & IsAFile ( GetJacketPath ( ) ) ;
}
bool Song : : HasDisc ( ) const
{
return m_sDiscFile ! = " " & & IsAFile ( GetDiscPath ( ) ) ;
}
bool Song : : HasCDImage ( ) const
{
return m_sCDFile ! = " " & & IsAFile ( GetCDImagePath ( ) ) ;
}
bool Song : : HasPreviewVid ( ) const
{
return m_sPreviewVidFile ! = " " & & IsAFile ( GetPreviewVidPath ( ) ) ;
}
const vector < BackgroundChange > & Song : : GetBackgroundChanges ( BackgroundLayer bl ) const
{
return * ( m_BackgroundChanges [ bl ] ) ;
}
vector < BackgroundChange > & Song : : GetBackgroundChanges ( BackgroundLayer bl )
{
return * ( m_BackgroundChanges [ bl ] . Get ( ) ) ;
}
const vector < BackgroundChange > & Song : : GetForegroundChanges ( ) const
{
return * m_ForegroundChanges ;
}
vector < BackgroundChange > & Song : : GetForegroundChanges ( )
{
return * m_ForegroundChanges . Get ( ) ;
}
vector < RString > Song : : GetChangesToVectorString ( const vector < BackgroundChange > & changes ) const
{
vector < RString > ret ;
FOREACH_CONST ( BackgroundChange , changes , bgc )
{
ret . push_back ( ( * bgc ) . ToString ( ) ) ;
}
return ret ;
}
vector < RString > Song : : GetBGChanges1ToVectorString ( ) const
{
return this - > GetChangesToVectorString ( this - > GetBackgroundChanges ( BACKGROUND_LAYER_1 ) ) ;
}
vector < RString > Song : : GetBGChanges2ToVectorString ( ) const
{
return this - > GetChangesToVectorString ( this - > GetBackgroundChanges ( BACKGROUND_LAYER_2 ) ) ;
}
vector < RString > Song : : GetFGChanges1ToVectorString ( ) const
{
return this - > GetChangesToVectorString ( this - > GetForegroundChanges ( ) ) ;
}
vector < RString > Song : : GetInstrumentTracksToVectorString ( ) const
{
vector < RString > ret ;
FOREACH_ENUM ( InstrumentTrack , it )
{
if ( this - > HasInstrumentTrack ( it ) )
{
ret . push_back ( InstrumentTrackToString ( it )
+ " = "
+ this - > m_sInstrumentTrackFile [ it ] ) ;
}
}
return ret ;
}
2015-03-02 18:44:01 -07:00
RString Song : : GetSongAssetPath ( RString sPath , const RString & sSongPath )
2012-07-01 12:24:39 -05:00
{
if ( sPath = = " " )
return RString ( ) ;
2013-10-12 23:50:44 -04:00
RString sRelPath = sSongPath + sPath ;
if ( DoesFileExist ( sRelPath ) )
return sRelPath ;
2012-07-01 12:24:39 -05:00
/* If there's no path in the file, the file is in the same directory as the
* song. (This is the preferred configuration.) */
if ( sPath . find ( ' / ' ) = = string : : npos )
2013-10-12 23:50:44 -04:00
return sRelPath ;
2012-07-01 12:24:39 -05:00
// The song contains a path; treat it as relative to the top SM directory.
if ( sPath . Left ( 3 ) = = " ../ " )
{
// The path begins with "../". Resolve it wrt. the song directory.
2013-10-12 23:50:44 -04:00
sPath = sRelPath ;
2012-07-01 12:24:39 -05:00
}
CollapsePath ( sPath ) ;
/* If the path still begins with "../", then there were an unreasonable number
* of them at the beginning of the path. Ignore the path entirely. */
if ( sPath . Left ( 3 ) = = " ../ " )
return RString ( ) ;
return sPath ;
}
/* Note that supplying a path relative to the top-level directory is only for
* compatibility with DWI. We prefer paths relative to the song directory. */
RString Song : : GetMusicPath ( ) const
{
return GetSongAssetPath ( m_sMusicFile , m_sSongDir ) ;
}
RString Song : : GetInstrumentTrackPath ( InstrumentTrack it ) const
{
return GetSongAssetPath ( m_sInstrumentTrackFile [ it ] , m_sSongDir ) ;
}
RString Song : : GetBannerPath ( ) const
{
return GetSongAssetPath ( m_sBannerFile , m_sSongDir ) ;
}
RString Song : : GetLyricsPath ( ) const
{
return GetSongAssetPath ( m_sLyricsFile , m_sSongDir ) ;
}
RString Song : : GetCDTitlePath ( ) const
{
return GetSongAssetPath ( m_sCDTitleFile , m_sSongDir ) ;
}
RString Song : : GetBackgroundPath ( ) const
{
return GetSongAssetPath ( m_sBackgroundFile , m_sSongDir ) ;
}
RString Song : : GetJacketPath ( ) const
{
return GetSongAssetPath ( m_sJacketFile , m_sSongDir ) ;
}
RString Song : : GetDiscPath ( ) const
{
return GetSongAssetPath ( m_sDiscFile , m_sSongDir ) ;
}
RString Song : : GetCDImagePath ( ) const
{
return GetSongAssetPath ( m_sCDFile , m_sSongDir ) ;
}
RString Song : : GetPreviewVidPath ( ) const
{
return GetSongAssetPath ( m_sPreviewVidFile , m_sSongDir ) ;
}
2015-03-01 15:40:16 -07:00
RString Song : : GetPreviewMusicPath ( ) const
{
2015-03-04 01:31:02 -07:00
if ( m_PreviewFile . empty ( ) )
2015-03-01 15:40:16 -07:00
{
2015-03-04 01:31:02 -07:00
return GetMusicPath ( ) ;
2015-03-01 15:40:16 -07:00
}
2015-03-04 01:31:02 -07:00
return GetSongAssetPath ( m_PreviewFile , m_sSongDir ) ;
2015-03-01 15:40:16 -07:00
}
float Song : : GetPreviewStartSeconds ( ) const
{
2015-03-04 01:31:02 -07:00
if ( m_PreviewFile . empty ( ) )
2015-03-01 15:40:16 -07:00
{
2015-03-04 01:31:02 -07:00
return m_fMusicSampleStartSeconds ;
2015-03-01 15:40:16 -07:00
}
2015-03-04 01:31:02 -07:00
return 0.0f ;
2015-03-01 15:40:16 -07:00
}
2012-07-01 12:24:39 -05:00
RString Song : : GetDisplayMainTitle ( ) const
{
if ( ! PREFSMAN - > m_bShowNativeLanguage ) return GetTranslitMainTitle ( ) ;
return m_sMainTitle ;
}
RString Song : : GetDisplaySubTitle ( ) const
{
if ( ! PREFSMAN - > m_bShowNativeLanguage ) return GetTranslitSubTitle ( ) ;
return m_sSubTitle ;
}
RString Song : : GetDisplayArtist ( ) const
{
if ( ! PREFSMAN - > m_bShowNativeLanguage ) return GetTranslitArtist ( ) ;
return m_sArtist ;
}
RString Song : : GetDisplayFullTitle ( ) const
{
RString Title = GetDisplayMainTitle ( ) ;
RString SubTitle = GetDisplaySubTitle ( ) ;
if ( ! SubTitle . empty ( ) ) Title + = " " + SubTitle ;
return Title ;
}
RString Song : : GetTranslitFullTitle ( ) const
{
RString Title = GetTranslitMainTitle ( ) ;
RString SubTitle = GetTranslitSubTitle ( ) ;
if ( ! SubTitle . empty ( ) ) Title + = " " + SubTitle ;
return Title ;
}
void Song : : AddSteps ( Steps * pSteps )
{
2014-10-20 05:14:54 -06:00
// Songs of unknown stepstype are saved as a forwards compatibility feature
// so that editing a simfile made by a future version that has a new style
// won't delete those steps. -Kyz
if ( pSteps - > m_StepsType ! = StepsType_Invalid )
{
m_vpSteps . push_back ( pSteps ) ;
ASSERT_M ( pSteps - > m_StepsType < NUM_StepsType , ssprintf ( " %i " , pSteps - > m_StepsType ) ) ;
m_vpStepsByType [ pSteps - > m_StepsType ] . push_back ( pSteps ) ;
}
else
{
m_UnknownStyleSteps . push_back ( pSteps ) ;
}
2012-07-01 12:24:39 -05:00
}
void Song : : DeleteSteps ( const Steps * pSteps , bool bReAutoGen )
{
ASSERT ( ! pSteps - > IsAutogen ( ) ) ;
// Avoid any stale Note::parent pointers by removing all AutoGen'd Steps,
// then adding them again.
if ( bReAutoGen )
RemoveAutoGenNotes ( ) ;
vector < Steps * > & vpSteps = m_vpStepsByType [ pSteps - > m_StepsType ] ;
for ( int j = vpSteps . size ( ) - 1 ; j > = 0 ; j - - )
{
if ( vpSteps [ j ] = = pSteps )
{
//delete vpSteps[j]; // delete below
vpSteps . erase ( vpSteps . begin ( ) + j ) ;
break ;
}
}
for ( int j = m_vpSteps . size ( ) - 1 ; j > = 0 ; j - - )
{
if ( m_vpSteps [ j ] = = pSteps )
{
delete m_vpSteps [ j ] ;
m_vpSteps . erase ( m_vpSteps . begin ( ) + j ) ;
break ;
}
}
if ( bReAutoGen )
AddAutoGenNotes ( ) ;
}
bool Song : : Matches ( RString sGroup , RString sSong ) const
{
if ( sGroup . size ( ) & & sGroup . CompareNoCase ( this - > m_sGroupName ) ! = 0 )
return false ;
RString sDir = this - > GetSongDir ( ) ;
sDir . Replace ( " \\ " , " / " ) ;
vector < RString > bits ;
split ( sDir , " / " , bits ) ;
ASSERT ( bits . size ( ) > = 2 ) ; // should always have at least two parts
const RString & sLastBit = bits [ bits . size ( ) - 1 ] ;
// match on song dir or title (ala DWI)
if ( ! sSong . CompareNoCase ( sLastBit ) )
return true ;
if ( ! sSong . CompareNoCase ( this - > GetTranslitFullTitle ( ) ) )
return true ;
return false ;
}
/* If apInUse is set, it contains a list of steps which are in use elsewhere,
* and should not be deleted. */
void Song : : FreeAllLoadedFromProfile ( ProfileSlot slot , const set < Steps * > * setInUse )
{
/* DeleteSteps will remove and recreate autogen notes, which may reorder
* m_vpSteps, so be careful not to skip over entries. */
vector < Steps * > apToRemove ;
for ( int s = m_vpSteps . size ( ) - 1 ; s > = 0 ; s - - )
{
Steps * pSteps = m_vpSteps [ s ] ;
if ( ! pSteps - > WasLoadedFromProfile ( ) )
continue ;
if ( slot ! = ProfileSlot_Invalid & & pSteps - > GetLoadedFromProfileSlot ( ) ! = slot )
continue ;
if ( setInUse ! = NULL & & setInUse - > find ( pSteps ) ! = setInUse - > end ( ) )
continue ;
apToRemove . push_back ( pSteps ) ;
}
for ( unsigned i = 0 ; i < apToRemove . size ( ) ; + + i )
this - > DeleteSteps ( apToRemove [ i ] ) ;
}
void Song : : GetStepsLoadedFromProfile ( ProfileSlot slot , vector < Steps * > & vpStepsOut ) const
{
for ( unsigned s = 0 ; s < m_vpSteps . size ( ) ; s + + )
{
Steps * pSteps = m_vpSteps [ s ] ;
if ( pSteps - > GetLoadedFromProfileSlot ( ) = = slot )
vpStepsOut . push_back ( pSteps ) ;
}
}
int Song : : GetNumStepsLoadedFromProfile ( ProfileSlot slot ) const
{
int iCount = 0 ;
for ( unsigned s = 0 ; s < m_vpSteps . size ( ) ; s + + )
{
Steps * pSteps = m_vpSteps [ s ] ;
if ( pSteps - > GetLoadedFromProfileSlot ( ) = = slot )
iCount + + ;
}
return iCount ;
}
bool Song : : IsEditAlreadyLoaded ( Steps * pSteps ) const
{
ASSERT_M ( pSteps - > GetDifficulty ( ) = = Difficulty_Edit ,
ssprintf ( " The %s chart for %s is no edit, thus it can't be checked for loading. " ,
DifficultyToString ( pSteps - > GetDifficulty ( ) ) . c_str ( ) ,
this - > m_sMainTitle . c_str ( ) ) ) ;
for ( unsigned i = 0 ; i < m_vpSteps . size ( ) ; i + + )
{
Steps * pOther = m_vpSteps [ i ] ;
if ( pOther - > GetDifficulty ( ) = = Difficulty_Edit & &
pOther - > m_StepsType = = pSteps - > m_StepsType & &
pOther - > GetHash ( ) = = pSteps - > GetHash ( ) )
{
return true ;
}
}
return false ;
}
bool Song : : IsStepsUsingDifferentTiming ( Steps * pSteps ) const
{
2013-01-23 14:51:18 -05:00
// XXX This no longer depends on Song at all
return ! pSteps - > m_Timing . empty ( ) ;
2012-07-01 12:24:39 -05:00
}
bool Song : : HasSignificantBpmChangesOrStops ( ) const
{
if ( m_SongTiming . HasStops ( ) | | m_SongTiming . HasDelays ( ) )
return true ;
// Don't consider BPM changes that only are only for maintaining sync as
// a real BpmChange.
if ( m_DisplayBPMType = = DISPLAY_BPM_SPECIFIED )
{
if ( m_fSpecifiedBPMMin ! = m_fSpecifiedBPMMax )
return true ;
}
else if ( m_SongTiming . HasBpmChanges ( ) )
{
return true ;
}
return false ;
}
float Song : : GetStepsSeconds ( ) const
{
return this - > GetLastSecond ( ) - this - > GetFirstSecond ( ) ;
}
bool Song : : IsLong ( ) const
{
return ! IsMarathon ( ) & & m_fMusicLengthSeconds > = g_fLongVerSongSeconds ;
}
bool Song : : IsMarathon ( ) const
{
return m_fMusicLengthSeconds > = g_fMarathonVerSongSeconds ;
}
// lua start
# include "LuaBinding.h"
/** @brief Allow Lua to have access to the Song. */
class LunaSong : public Luna < Song >
{
public :
static int GetDisplayFullTitle ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetDisplayFullTitle ( ) ) ; return 1 ;
}
static int GetTranslitFullTitle ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetTranslitFullTitle ( ) ) ; return 1 ;
}
static int GetDisplayMainTitle ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetDisplayMainTitle ( ) ) ; return 1 ;
}
static int GetTranslitMainTitle ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetTranslitMainTitle ( ) ) ; return 1 ;
}
static int GetDisplaySubTitle ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetDisplaySubTitle ( ) ) ; return 1 ;
}
static int GetTranslitSubTitle ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetTranslitSubTitle ( ) ) ; return 1 ;
}
static int GetDisplayArtist ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetDisplayArtist ( ) ) ; return 1 ;
}
static int GetTranslitArtist ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetTranslitArtist ( ) ) ; return 1 ;
}
static int GetGenre ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > m_sGenre ) ; return 1 ;
}
static int GetOrigin ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > m_sOrigin ) ; return 1 ;
}
static int GetAllSteps ( T * p , lua_State * L )
{
const vector < Steps * > & v = p - > GetAllSteps ( ) ;
LuaHelpers : : CreateTableFromArray < Steps * > ( v , L ) ;
return 1 ;
}
static int GetStepsByStepsType ( T * p , lua_State * L )
{
StepsType st = Enum : : Check < StepsType > ( L , 1 ) ;
const vector < Steps * > & v = p - > GetStepsByStepsType ( st ) ;
LuaHelpers : : CreateTableFromArray < Steps * > ( v , L ) ;
return 1 ;
}
static int GetSongDir ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetSongDir ( ) ) ;
return 1 ;
}
static int GetMusicPath ( T * p , lua_State * L )
{
RString s = p - > GetMusicPath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetBannerPath ( T * p , lua_State * L )
{
RString s = p - > GetBannerPath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetBackgroundPath ( T * p , lua_State * L )
{
RString s = p - > GetBackgroundPath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetPreviewVidPath ( T * p , lua_State * L )
{
RString s = p - > GetPreviewVidPath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
2015-03-04 01:31:02 -07:00
static int GetPreviewMusicPath ( T * p , lua_State * L )
{
RString s = p - > GetPreviewMusicPath ( ) ;
lua_pushstring ( L , s ) ;
return 1 ;
}
2012-07-01 12:24:39 -05:00
static int GetJacketPath ( T * p , lua_State * L )
{
RString s = p - > GetJacketPath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetCDImagePath ( T * p , lua_State * L )
{
RString s = p - > GetCDImagePath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetDiscPath ( T * p , lua_State * L )
{
RString s = p - > GetDiscPath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetCDTitlePath ( T * p , lua_State * L )
{
RString s = p - > GetCDTitlePath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetLyricsPath ( T * p , lua_State * L )
{
RString s = p - > GetLyricsPath ( ) ;
if ( ! s . empty ( ) )
lua_pushstring ( L , s ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetSongFilePath ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > GetSongFilePath ( ) ) ;
return 1 ;
}
static int IsTutorial ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > IsTutorial ( ) ) ;
return 1 ;
}
static int IsEnabled ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > GetEnabled ( ) ) ;
return 1 ;
}
static int GetGroupName ( T * p , lua_State * L )
{
lua_pushstring ( L , p - > m_sGroupName ) ;
return 1 ;
}
static int MusicLengthSeconds ( T * p , lua_State * L )
{
lua_pushnumber ( L , p - > m_fMusicLengthSeconds ) ;
return 1 ;
}
static int GetSampleStart ( T * p , lua_State * L )
{
2015-03-04 01:31:02 -07:00
lua_pushnumber ( L , p - > GetPreviewStartSeconds ( ) ) ;
2012-07-01 12:24:39 -05:00
return 1 ;
}
static int GetSampleLength ( T * p , lua_State * L )
{
lua_pushnumber ( L , p - > m_fMusicSampleLengthSeconds ) ;
return 1 ;
}
static int IsLong ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > IsLong ( ) ) ;
return 1 ;
}
static int IsMarathon ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > IsMarathon ( ) ) ;
return 1 ;
}
static int HasStepsType ( T * p , lua_State * L )
{
StepsType st = Enum : : Check < StepsType > ( L , 1 ) ;
lua_pushboolean ( L , p - > HasStepsType ( st ) ) ;
return 1 ;
}
static int HasStepsTypeAndDifficulty ( T * p , lua_State * L )
{
StepsType st = Enum : : Check < StepsType > ( L , 1 ) ;
Difficulty dc = Enum : : Check < Difficulty > ( L , 2 ) ;
lua_pushboolean ( L , p - > HasStepsTypeAndDifficulty ( st , dc ) ) ;
return 1 ;
}
static int IsStepsUsingDifferentTiming ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > IsStepsUsingDifferentTiming ( Luna < Steps > : : check ( L , 1 , true ) ) ) ;
return 1 ;
}
/* TODO: HasStepsTypeAndDifficulty and GetOneSteps should be in
* a SongUtil Lua table and a method of Steps. */
static int GetOneSteps ( T * p , lua_State * L )
{
StepsType st = Enum : : Check < StepsType > ( L , 1 ) ;
Difficulty dc = Enum : : Check < Difficulty > ( L , 2 ) ;
Steps * pSteps = SongUtil : : GetOneSteps ( p , st , dc ) ;
if ( pSteps )
pSteps - > PushSelf ( L ) ;
else
lua_pushnil ( L ) ;
return 1 ;
}
static int GetTimingData ( T * p , lua_State * L ) { p - > m_SongTiming . PushSelf ( L ) ; return 1 ; }
2015-02-02 23:28:33 -07:00
static int GetBGChanges ( T * p , lua_State * L )
{
const vector < BackgroundChange > & changes = p - > GetBackgroundChanges ( BACKGROUND_LAYER_1 ) ;
lua_createtable ( L , changes . size ( ) , 0 ) ;
for ( size_t c = 0 ; c < changes . size ( ) ; + + c )
{
lua_createtable ( L , 0 , 8 ) ;
lua_pushnumber ( L , changes [ c ] . m_fStartBeat ) ;
lua_setfield ( L , - 2 , " start_beat " ) ;
lua_pushnumber ( L , changes [ c ] . m_fRate ) ;
lua_setfield ( L , - 2 , " rate " ) ;
LuaHelpers : : Push ( L , changes [ c ] . m_sTransition ) ;
lua_setfield ( L , - 2 , " transition " ) ;
LuaHelpers : : Push ( L , changes [ c ] . m_def . m_sEffect ) ;
lua_setfield ( L , - 2 , " effect " ) ;
LuaHelpers : : Push ( L , changes [ c ] . m_def . m_sFile1 ) ;
lua_setfield ( L , - 2 , " file1 " ) ;
LuaHelpers : : Push ( L , changes [ c ] . m_def . m_sFile2 ) ;
lua_setfield ( L , - 2 , " file2 " ) ;
LuaHelpers : : Push ( L , changes [ c ] . m_def . m_sColor1 ) ;
lua_setfield ( L , - 2 , " color1 " ) ;
LuaHelpers : : Push ( L , changes [ c ] . m_def . m_sColor2 ) ;
lua_setfield ( L , - 2 , " color2 " ) ;
lua_rawseti ( L , - 2 , c + 1 ) ;
}
return 1 ;
}
2012-07-01 12:24:39 -05:00
// has functions
static int HasMusic ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasMusic ( ) ) ; return 1 ; }
static int HasBanner ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasBanner ( ) ) ; return 1 ; }
static int HasBackground ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasBackground ( ) ) ; return 1 ; }
static int HasPreviewVid ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasPreviewVid ( ) ) ; return 1 ; }
static int HasJacket ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasJacket ( ) ) ; return 1 ; }
static int HasDisc ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasDisc ( ) ) ; return 1 ; }
static int HasCDImage ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasCDImage ( ) ) ; return 1 ; }
static int HasCDTitle ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasCDTitle ( ) ) ; return 1 ; }
static int HasBGChanges ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasBGChanges ( ) ) ; return 1 ; }
static int HasLyrics ( T * p , lua_State * L ) { lua_pushboolean ( L , p - > HasLyrics ( ) ) ; return 1 ; }
// functions that AJ loves
static int HasSignificantBPMChangesOrStops ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > HasSignificantBpmChangesOrStops ( ) ) ;
return 1 ;
}
static int HasEdits ( T * p , lua_State * L )
{
StepsType st = Enum : : Check < StepsType > ( L , 1 ) ;
lua_pushboolean ( L , p - > HasEdits ( st ) ) ;
return 1 ;
}
static int IsEasy ( T * p , lua_State * L )
{
StepsType st = Enum : : Check < StepsType > ( L , 1 ) ;
lua_pushboolean ( L , p - > IsEasy ( st ) ) ;
return 1 ;
}
static int GetStepsSeconds ( T * p , lua_State * L )
{
lua_pushnumber ( L , p - > GetStepsSeconds ( ) ) ;
return 1 ;
}
static int NormallyDisplayed ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > NormallyDisplayed ( ) ) ;
return 1 ;
}
static int ShowInDemonstrationAndRanking ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > ShowInDemonstrationAndRanking ( ) ) ;
return 1 ;
}
static int GetFirstSecond ( T * p , lua_State * L )
{
lua_pushnumber ( L , p - > GetFirstSecond ( ) ) ;
return 1 ;
}
static int GetLastSecond ( T * p , lua_State * L )
{
lua_pushnumber ( L , p - > GetLastSecond ( ) ) ;
return 1 ;
}
static int GetFirstBeat ( T * p , lua_State * L )
{
lua_pushnumber ( L , p - > GetFirstBeat ( ) ) ;
return 1 ;
}
static int GetLastBeat ( T * p , lua_State * L )
{
lua_pushnumber ( L , p - > GetLastBeat ( ) ) ;
return 1 ;
}
static int HasAttacks ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > HasAttacks ( ) ) ;
return 1 ;
}
static int GetDisplayBpms ( T * p , lua_State * L )
{
DisplayBpms temp ;
p - > GetDisplayBpms ( temp ) ;
float fMin = temp . GetMin ( ) ;
float fMax = temp . GetMax ( ) ;
vector < float > fBPMs ;
fBPMs . push_back ( fMin ) ;
fBPMs . push_back ( fMax ) ;
LuaHelpers : : CreateTableFromArray ( fBPMs , L ) ;
return 1 ;
}
static int IsDisplayBpmSecret ( T * p , lua_State * L )
{
DisplayBpms temp ;
p - > GetDisplayBpms ( temp ) ;
lua_pushboolean ( L , temp . IsSecret ( ) ) ;
return 1 ;
}
static int IsDisplayBpmConstant ( T * p , lua_State * L )
{
DisplayBpms temp ;
p - > GetDisplayBpms ( temp ) ;
lua_pushboolean ( L , temp . BpmIsConstant ( ) ) ;
return 1 ;
}
static int IsDisplayBpmRandom ( T * p , lua_State * L )
{
lua_pushboolean ( L , p - > m_DisplayBPMType = = DISPLAY_BPM_RANDOM ) ;
return 1 ;
}
2015-03-30 17:45:52 -06:00
static int ReloadFromSongDir ( T * p , lua_State * L )
{
p - > ReloadFromSongDir ( ) ;
COMMON_RETURN_SELF ;
}
2012-07-01 12:24:39 -05:00
LunaSong ( )
{
ADD_METHOD ( GetDisplayFullTitle ) ;
ADD_METHOD ( GetTranslitFullTitle ) ;
ADD_METHOD ( GetDisplayMainTitle ) ;
ADD_METHOD ( GetTranslitMainTitle ) ;
ADD_METHOD ( GetDisplaySubTitle ) ;
ADD_METHOD ( GetTranslitSubTitle ) ;
ADD_METHOD ( GetDisplayArtist ) ;
ADD_METHOD ( GetTranslitArtist ) ;
ADD_METHOD ( GetGenre ) ;
ADD_METHOD ( GetOrigin ) ;
ADD_METHOD ( GetAllSteps ) ;
ADD_METHOD ( GetStepsByStepsType ) ;
ADD_METHOD ( GetSongDir ) ;
ADD_METHOD ( GetMusicPath ) ;
ADD_METHOD ( GetBannerPath ) ;
ADD_METHOD ( GetBackgroundPath ) ;
ADD_METHOD ( GetJacketPath ) ;
ADD_METHOD ( GetCDImagePath ) ;
ADD_METHOD ( GetDiscPath ) ;
ADD_METHOD ( GetCDTitlePath ) ;
ADD_METHOD ( GetLyricsPath ) ;
ADD_METHOD ( GetSongFilePath ) ;
ADD_METHOD ( IsTutorial ) ;
ADD_METHOD ( IsEnabled ) ;
ADD_METHOD ( GetGroupName ) ;
ADD_METHOD ( MusicLengthSeconds ) ;
ADD_METHOD ( GetSampleStart ) ;
ADD_METHOD ( GetSampleLength ) ;
ADD_METHOD ( IsLong ) ;
ADD_METHOD ( IsMarathon ) ;
ADD_METHOD ( HasStepsType ) ;
ADD_METHOD ( HasStepsTypeAndDifficulty ) ;
ADD_METHOD ( GetOneSteps ) ;
ADD_METHOD ( GetTimingData ) ;
2015-02-02 23:28:33 -07:00
ADD_METHOD ( GetBGChanges ) ;
2012-07-01 12:24:39 -05:00
ADD_METHOD ( HasMusic ) ;
ADD_METHOD ( HasBanner ) ;
ADD_METHOD ( HasBackground ) ;
ADD_METHOD ( HasJacket ) ;
ADD_METHOD ( HasCDImage ) ;
ADD_METHOD ( HasDisc ) ;
ADD_METHOD ( HasCDTitle ) ;
ADD_METHOD ( HasBGChanges ) ;
ADD_METHOD ( HasLyrics ) ;
ADD_METHOD ( HasSignificantBPMChangesOrStops ) ;
ADD_METHOD ( HasEdits ) ;
ADD_METHOD ( IsEasy ) ;
ADD_METHOD ( GetStepsSeconds ) ;
ADD_METHOD ( NormallyDisplayed ) ;
ADD_METHOD ( GetFirstBeat ) ;
ADD_METHOD ( GetFirstSecond ) ;
ADD_METHOD ( GetLastBeat ) ;
ADD_METHOD ( GetLastSecond ) ;
ADD_METHOD ( HasAttacks ) ;
ADD_METHOD ( GetDisplayBpms ) ;
ADD_METHOD ( IsDisplayBpmSecret ) ;
ADD_METHOD ( IsDisplayBpmConstant ) ;
ADD_METHOD ( IsDisplayBpmRandom ) ;
ADD_METHOD ( IsStepsUsingDifferentTiming ) ;
ADD_METHOD ( ShowInDemonstrationAndRanking ) ;
ADD_METHOD ( HasPreviewVid ) ;
ADD_METHOD ( GetPreviewVidPath ) ;
2015-03-04 01:31:02 -07:00
ADD_METHOD ( GetPreviewMusicPath ) ;
2015-03-30 17:45:52 -06:00
ADD_METHOD ( ReloadFromSongDir ) ;
2012-07-01 12:24:39 -05:00
}
} ;
LUA_REGISTER_CLASS ( Song )
// lua end
/*
* (c) 2001-2004 Chris Danford, Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/