diff --git a/stepmania/src/Course.cpp b/stepmania/src/Course.cpp index 9ae36a9092..c96fbbe261 100644 --- a/stepmania/src/Course.cpp +++ b/stepmania/src/Course.cpp @@ -8,7 +8,6 @@ #include "GameState.h" #include "RageException.h" #include "RageLog.h" -#include "MsdFile.h" #include "PlayerOptions.h" #include "SongOptions.h" #include "SongCacheIndex.h" @@ -22,6 +21,7 @@ #include "Foreach.h" #include "UnlockManager.h" #include +#include "CourseLoaderCRS.h" static const CString CourseTypeNames[] = { @@ -124,306 +124,12 @@ PlayMode Course::GetPlayMode() const } } -void Course::LoadFromCRSFile( CString sPath ) -{ - Init(); - - m_sPath = sPath; // save path - - // save group name - { - CStringArray parts; - split( sPath, "/", parts, false ); - if( parts.size() >= 4 ) // e.g. "/Courses/blah/fun.cvs" - m_sGroupName = parts[parts.size()-2]; - } - - - bool bUseCache = true; - { - /* First look in the cache for this course. Don't bother - * honoring FastLoad for checking the course hash, since - * courses are normally grouped into a few directories, not - * one directory per course. XXX: if !FastLoad, regen - * cache if the used songs have changed */ - unsigned uHash = SONGINDEX->GetCacheHash( m_sPath ); - if( !DoesFileExist(GetCacheFilePath()) ) - bUseCache = false; - if( !PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sPath) != uHash ) - bUseCache = false; // this cache is out of date - } - - if( bUseCache ) - { - CString sCacheFile = GetCacheFilePath(); - LOG->Trace( "Course::LoadFromCRSFile(\"%s\") (\"%s\")", sPath.c_str(), sCacheFile.c_str() ); - sPath = sCacheFile.c_str(); - } - else - { - LOG->Trace( "Course::LoadFromCRSFile(\"%s\")", sPath.c_str() ); - } - - MsdFile msd; - if( !msd.ReadFile(sPath) ) - RageException::Throw( "Error opening CRS file '%s'.", sPath.c_str() ); - - const CString sFName = SetExtension( m_sPath, "" ); - - CStringArray arrayPossibleBanners; - GetDirListing( sFName + "*.png", arrayPossibleBanners, false, true ); - GetDirListing( sFName + "*.jpg", arrayPossibleBanners, false, true ); - GetDirListing( sFName + "*.bmp", arrayPossibleBanners, false, true ); - GetDirListing( sFName + "*.gif", arrayPossibleBanners, false, true ); - if( !arrayPossibleBanners.empty() ) - m_sBannerPath = arrayPossibleBanners[0]; - - AttackArray attacks; - float fGainSeconds = 0; - for( unsigned i=0; iWarn( "Course file '%s' contains an invalid #METER string: \"%s\"", - sPath.c_str(), sParams[1].c_str() ); - continue; - } - m_iCustomMeter[cd] = atoi( sParams[2] ); - } - } - - else if( 0 == stricmp(sValueName, "MODS") ) - { - Attack attack; - float end = -9999; - for( unsigned j = 1; j < sParams.params.size(); ++j ) - { - CStringArray sBits; - split( sParams[j], "=", sBits, false ); - if( sBits.size() < 2 ) - continue; - - TrimLeft( sBits[0] ); - TrimRight( sBits[0] ); - if( !sBits[0].CompareNoCase("TIME") ) - attack.fStartSecond = strtof( sBits[1], NULL ); - else if( !sBits[0].CompareNoCase("LEN") ) - attack.fSecsRemaining = strtof( sBits[1], NULL ); - else if( !sBits[0].CompareNoCase("END") ) - end = strtof( sBits[1], NULL ); - else if( !sBits[0].CompareNoCase("MODS") ) - { - attack.sModifiers = sBits[1]; - if( end != -9999 ) - { - ASSERT_M( end >= attack.fStartSecond, ssprintf("Attack ends before it starts. end %f, start %f", end, attack.fStartSecond) ); - attack.fSecsRemaining = end - attack.fStartSecond; - end = -9999; - } - - // warn on invalid so we catch bogus mods on load - PlayerOptions po; - po.FromString( attack.sModifiers, true ); - - attacks.push_back( attack ); - } - else - { - LOG->Warn( "Unexpected value named '%s'", sBits[0].c_str() ); - } - } - - - } - else if( 0 == stricmp(sValueName, "SONG") ) - { - CourseEntry new_entry; - - // infer entry::Type from the first param - if( sParams[1].Left(strlen("BEST")) == "BEST" ) - { - new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; - CLAMP( new_entry.iChooseIndex, 0, 500 ); - new_entry.songSort = SongSort_MostPlays; - } - else if( sParams[1].Left(strlen("WORST")) == "WORST" ) - { - new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1; - CLAMP( new_entry.iChooseIndex, 0, 500 ); - new_entry.songSort = SongSort_FewestPlays; - } - else if( sParams[1] == "*" ) - { - new_entry.bSecret = true; - } - else if( sParams[1].Right(1) == "*" ) - { - new_entry.bSecret = true; - CString sSong = sParams[1]; - sSong.Replace( "\\", "/" ); - CStringArray bits; - split( sSong, "/", bits ); - if( bits.size() == 2 ) - { - new_entry.sSongGroup = bits[0]; - } - else - { - LOG->Warn( "Course file '%s' contains a random_within_group entry '%s' that is invalid. " - "Song should be in the format '/*'.", - sPath.c_str(), sSong.c_str()); - } - - if( !SONGMAN->DoesSongGroupExist(new_entry.sSongGroup) ) - { - /* XXX: We need a place to put "user warnings". This is too loud for info.txt-- - * it obscures important warnings--and regular users never look there, anyway. */ - LOG->Trace( "Course file '%s' random_within_group entry '%s' specifies a group that doesn't exist. " - "This entry will be ignored.", - sPath.c_str(), sSong.c_str()); - continue; // skip this #SONG - } - } - else - { - CString sSong = sParams[1]; - new_entry.pSong = SONGMAN->FindSong( sSong ); - - if( new_entry.pSong == NULL ) - { - /* XXX: We need a place to put "user warnings". This is too loud for info.txt-- - * it obscures important warnings--and regular users never look there, anyway. */ - LOG->Trace( "Course file '%s' contains a fixed song entry '%s' that does not exist. " - "This entry will be ignored.", - sPath.c_str(), sSong.c_str()); - continue; // skip this #SONG - } - } - - new_entry.baseDifficulty = StringToDifficulty( sParams[2] ); - if( new_entry.baseDifficulty == DIFFICULTY_INVALID ) - { - int retval = sscanf( sParams[2], "%d..%d", &new_entry.iLowMeter, &new_entry.iHighMeter ); - if( retval == 1 ) - new_entry.iHighMeter = new_entry.iLowMeter; - else if( retval != 2 ) - { - LOG->Warn("Course file '%s' contains an invalid difficulty setting: \"%s\", 3..6 used instead", - sPath.c_str(), sParams[2].c_str()); - new_entry.iLowMeter = 3; - new_entry.iHighMeter = 6; - } - } - - { - /* If "showcourse" or "noshowcourse" is in the list, force new_entry.secret - * on or off. */ - CStringArray mods; - split( sParams[3], ",", mods, true ); - for( int j = (int) mods.size()-1; j >= 0 ; --j ) - { - CString &sMod = mods[j]; - TrimLeft( sMod ); - TrimRight( sMod ); - if( !sMod.CompareNoCase("showcourse") ) - new_entry.bSecret = false; - else if( !sMod.CompareNoCase("noshowcourse") ) - new_entry.bSecret = true; - else if( !sMod.CompareNoCase("nodifficult") ) - new_entry.bNoDifficult = true; - else - continue; - mods.erase(mods.begin() + j); - } - new_entry.sModifiers = join( ",", mods ); - } - - new_entry.attacks = attacks; - new_entry.fGainSeconds = fGainSeconds; - attacks.clear(); - - m_vEntries.push_back( new_entry ); - } - else if( bUseCache && !stricmp(sValueName, "RADAR") ) - { - StepsType st = (StepsType) atoi(sParams[1]); - CourseDifficulty cd = (CourseDifficulty) atoi(sParams[2]); - - RadarValues rv; - rv.FromString( sParams[3] ); - m_RadarCache[CacheEntry(st, cd)] = rv; - } - else - { - LOG->Warn( "Unexpected value named '%s'", sValueName.c_str() ); - } - } - static TitleSubst tsub("courses"); - - TitleFields title; - title.Title = m_sMainTitle; - title.TitleTranslit = m_sMainTitleTranslit; - tsub.Subst( title ); - m_sMainTitle = title.Title; - m_sMainTitleTranslit = title.TitleTranslit; - - /* Cache and load the course banner. Only bother doing this if at least one - * song was found in the course. */ - if( m_sBannerPath != "" && !m_vEntries.empty() ) - BANNERCACHE->CacheBanner( m_sBannerPath ); - - /* Cache each trail RadarValues that's slow to load, so we - * don't have to do it at runtime. */ - if( !bUseCache ) - { - CalculateRadarValues(); - - /* If we have any cache data, write the cache file. */ - if( m_RadarCache.size() ) - { - CString sCachePath = GetCacheFilePath(); - Save( sCachePath, true ); - - SONGINDEX->AddCacheIndex( m_sPath, GetHashForFile(m_sPath) ); - } - } -} - void Course::RevertFromDisk() { // trying to catch invalid an Course ASSERT( !m_sPath.empty() ); - LoadFromCRSFile( m_sPath ); + CourseLoaderCRS::LoadFromCRSFile( m_sPath, *this ); } CString Course::GetCacheFilePath() const @@ -454,139 +160,6 @@ void Course::Init() m_iTrailCacheSeed = 0; } -void Course::Save( CString sPath, bool bSavingCache ) -{ - ASSERT( !m_bIsAutogen ); - - /* By default, save to the file we loaded from. */ - if( sPath == "" ) - sPath = m_sPath; - - RageFile f; - if( !f.Open( sPath, RageFile::WRITE ) ) - { - LOG->Warn( "Could not write course file '%s': %s", sPath.c_str(), f.GetError().c_str() ); - return; - } - - f.PutLine( ssprintf("#COURSE:%s;", m_sMainTitle.c_str()) ); - if( m_sMainTitleTranslit != "" ) - f.PutLine( ssprintf("#COURSETRANSLIT:%s;", m_sMainTitleTranslit.c_str()) ); - if( m_bRepeat ) - f.PutLine( "#REPEAT:YES;" ); - if( m_iLives != -1 ) - f.PutLine( ssprintf("#LIVES:%i;", m_iLives) ); - - FOREACH_CourseDifficulty( cd ) - { - if( m_iCustomMeter[cd] == -1 ) - continue; - f.PutLine( ssprintf("#METER:%s:%i;", CourseDifficultyToString(cd).c_str(), m_iCustomMeter[cd]) ); - } - - if( bSavingCache ) - { - f.PutLine( "// cache tags:" ); - - RadarCache_t::const_iterator it; - for( it = m_RadarCache.begin(); it != m_RadarCache.end(); ++it ) - { - // #RADAR:type:difficulty:value,value,value...; - const CacheEntry &entry = it->first; - StepsType st = entry.first; - CourseDifficulty cd = entry.second; - - CStringArray asRadarValues; - const RadarValues &rv = it->second; - for( int r=0; r < NUM_RADAR_CATEGORIES; r++ ) - asRadarValues.push_back( ssprintf("%.3f", rv[r]) ); - CString sLine = ssprintf( "#RADAR:%i:%i:", st, cd ); - sLine += join( ",", asRadarValues ) + ";"; - f.PutLine( sLine ); - } - f.PutLine( "// end cache tags" ); - } - - for( unsigned i=0; i 0 ) - f.PutLine( ssprintf("#GAINSECONDS:%f;", entry.fGainSeconds) ); - - if( entry.songSort == SongSort_MostPlays && entry.iChooseIndex != -1 ) - { - f.Write( ssprintf( "#SONG:BEST%d", entry.iChooseIndex+1 ) ); - } - else if( entry.songSort == SongSort_FewestPlays && entry.iChooseIndex != -1 ) - { - f.Write( ssprintf( "#SONG:WORST%d", entry.iChooseIndex+1 ) ); - } - else if( entry.pSong ) - { - // strip off everything but the group name and song dir - CStringArray as; - ASSERT( entry.pSong != NULL ); - split( entry.pSong->GetSongDir(), "/", as ); - ASSERT( as.size() >= 2 ); - CString sGroup = as[ as.size()-2 ]; - CString sSong = as[ as.size()-1 ]; - f.Write( "#SONG:" + sGroup + '/' + sSong ); - } - else if( !entry.sSongGroup.empty() ) - { - f.Write( ssprintf( "#SONG:%s/*", entry.sSongGroup.c_str() ) ); - } - else - { - f.Write( "#SONG:*" ); - } - - f.Write( ":" ); - if( entry.baseDifficulty != DIFFICULTY_INVALID ) - f.Write( DifficultyToString(entry.baseDifficulty) ); - else if( entry.iLowMeter != -1 && entry.iHighMeter != -1 ) - f.Write( ssprintf( "%d..%d", entry.iLowMeter, entry.iHighMeter ) ); - f.Write( ":" ); - - CString sModifiers = entry.sModifiers; - bool bDefaultSecret = entry.IsRandomSong(); - if( bDefaultSecret != entry.bSecret ) - { - if( sModifiers != "" ) - sModifiers += ","; - sModifiers += entry.bSecret? "noshowcourse":"showcourse"; - } - - if( entry.bNoDifficult ) - { - if( sModifiers != "" ) - sModifiers += ","; - sModifiers += "nodifficult"; - } - f.Write( sModifiers ); - - f.PutLine( ";" ); - } -} - - void Course::AutogenEndlessFromGroup( CString sGroupName, Difficulty diff ) { m_bIsAutogen = true; diff --git a/stepmania/src/Course.h b/stepmania/src/Course.h index baa541441c..58ed0df0ef 100644 --- a/stepmania/src/Course.h +++ b/stepmania/src/Course.h @@ -108,11 +108,10 @@ public: bool m_bIsAutogen; // was this created by AutoGen? CString m_sPath; -private: + CString m_sMainTitle, m_sMainTitleTranslit; CString m_sSubTitle, m_sSubTitleTranslit; -public: bool HasBanner() const; CString m_sBannerPath; @@ -164,10 +163,8 @@ public: bool ShowInDemonstrationAndRanking() const; - void LoadFromCRSFile( CString sPath ); void RevertFromDisk(); void Init(); - void Save( CString sPath = "", bool bSavingCache=false ); /* default is source file */ void AutogenEndlessFromGroup( CString sGroupName, Difficulty dc ); void AutogenNonstopFromGroup( CString sGroupName, Difficulty dc ); void AutogenOniFromArtist( CString sArtistName, CString sArtistNameTranslit, vector aSongs, Difficulty dc ); @@ -196,7 +193,6 @@ public: // Lua void PushSelf( lua_State *L ); -private: void CalculateRadarValues(); bool GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const; diff --git a/stepmania/src/CourseLoaderCRS.cpp b/stepmania/src/CourseLoaderCRS.cpp new file mode 100644 index 0000000000..dbe216fafc --- /dev/null +++ b/stepmania/src/CourseLoaderCRS.cpp @@ -0,0 +1,370 @@ +#include "global.h" +#include "CourseLoaderCRS.h" +#include "RageLog.h" +#include "Course.h" +#include "RageUtil.h" +#include "SongCacheIndex.h" +#include "PrefsManager.h" +#include "MsdFile.h" +#include "PlayerOptions.h" +#include "SongManager.h" +#include "TitleSubstitution.h" +#include "BannerCache.h" +#include "RageFileManager.h" +#include "Profile.h" +#include "CourseWriterCRS.h" + +const int MAX_EDIT_COURSE_SIZE_BYTES = 30*1024; // 30KB + +bool CourseLoaderCRS::LoadFromCRSFile( const CString &_sPath, Course &out ) +{ + CString sPath = _sPath; + + out.Init(); + + out.m_sPath = sPath; // save path + + // save group name + { + CStringArray parts; + split( sPath, "/", parts, false ); + if( parts.size() >= 4 ) // e.g. "/Courses/blah/fun.cvs" + out.m_sGroupName = parts[parts.size()-2]; + } + + + bool bUseCache = true; + { + /* First look in the cache for this course. Don't bother + * honoring FastLoad for checking the course hash, since + * courses are normally grouped into a few directories, not + * one directory per course. XXX: if !FastLoad, regen + * cache if the used songs have changed */ + unsigned uHash = SONGINDEX->GetCacheHash( out.m_sPath ); + if( !DoesFileExist(out.GetCacheFilePath()) ) + bUseCache = false; + if( !PREFSMAN->m_bFastLoad && GetHashForDirectory(out.m_sPath) != uHash ) + bUseCache = false; // this cache is out of date + } + + if( bUseCache ) + { + CString sCacheFile = out.GetCacheFilePath(); + LOG->Trace( "CourseLoaderCRS::LoadFromCRSFile(\"%s\") (\"%s\")", sPath.c_str(), sCacheFile.c_str() ); + sPath = sCacheFile.c_str(); + } + else + { + LOG->Trace( "CourseLoaderCRS::LoadFromCRSFile(\"%s\")", sPath.c_str() ); + } + + MsdFile msd; + if( !msd.ReadFile(sPath) ) + RageException::Throw( "Error opening CRS file '%s'.", sPath.c_str() ); + + const CString sFName = SetExtension( out.m_sPath, "" ); + + CStringArray arrayPossibleBanners; + GetDirListing( sFName + "*.png", arrayPossibleBanners, false, true ); + GetDirListing( sFName + "*.jpg", arrayPossibleBanners, false, true ); + GetDirListing( sFName + "*.bmp", arrayPossibleBanners, false, true ); + GetDirListing( sFName + "*.gif", arrayPossibleBanners, false, true ); + if( !arrayPossibleBanners.empty() ) + out.m_sBannerPath = arrayPossibleBanners[0]; + + AttackArray attacks; + float fGainSeconds = 0; + for( unsigned i=0; iWarn( "Course file '%s' contains an invalid #METER string: \"%s\"", + sPath.c_str(), sParams[1].c_str() ); + continue; + } + out.m_iCustomMeter[cd] = atoi( sParams[2] ); + } + } + + else if( 0 == stricmp(sValueName, "MODS") ) + { + Attack attack; + float end = -9999; + for( unsigned j = 1; j < sParams.params.size(); ++j ) + { + CStringArray sBits; + split( sParams[j], "=", sBits, false ); + if( sBits.size() < 2 ) + continue; + + TrimLeft( sBits[0] ); + TrimRight( sBits[0] ); + if( !sBits[0].CompareNoCase("TIME") ) + attack.fStartSecond = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("LEN") ) + attack.fSecsRemaining = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("END") ) + end = strtof( sBits[1], NULL ); + else if( !sBits[0].CompareNoCase("MODS") ) + { + attack.sModifiers = sBits[1]; + if( end != -9999 ) + { + ASSERT_M( end >= attack.fStartSecond, ssprintf("Attack ends before it starts. end %f, start %f", end, attack.fStartSecond) ); + attack.fSecsRemaining = end - attack.fStartSecond; + end = -9999; + } + + // warn on invalid so we catch bogus mods on load + PlayerOptions po; + po.FromString( attack.sModifiers, true ); + + attacks.push_back( attack ); + } + else + { + LOG->Warn( "Unexpected value named '%s'", sBits[0].c_str() ); + } + } + + + } + else if( 0 == stricmp(sValueName, "SONG") ) + { + CourseEntry new_entry; + + // infer entry::Type from the first param + if( sParams[1].Left(strlen("BEST")) == "BEST" ) + { + new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; + CLAMP( new_entry.iChooseIndex, 0, 500 ); + new_entry.songSort = SongSort_MostPlays; + } + else if( sParams[1].Left(strlen("WORST")) == "WORST" ) + { + new_entry.iChooseIndex = atoi( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1; + CLAMP( new_entry.iChooseIndex, 0, 500 ); + new_entry.songSort = SongSort_FewestPlays; + } + else if( sParams[1] == "*" ) + { + new_entry.bSecret = true; + } + else if( sParams[1].Right(1) == "*" ) + { + new_entry.bSecret = true; + CString sSong = sParams[1]; + sSong.Replace( "\\", "/" ); + CStringArray bits; + split( sSong, "/", bits ); + if( bits.size() == 2 ) + { + new_entry.sSongGroup = bits[0]; + } + else + { + LOG->Warn( "Course file '%s' contains a random_within_group entry '%s' that is invalid. " + "Song should be in the format '/*'.", + sPath.c_str(), sSong.c_str()); + } + + if( !SONGMAN->DoesSongGroupExist(new_entry.sSongGroup) ) + { + /* XXX: We need a place to put "user warnings". This is too loud for info.txt-- + * it obscures important warnings--and regular users never look there, anyway. */ + LOG->Trace( "Course file '%s' random_within_group entry '%s' specifies a group that doesn't exist. " + "This entry will be ignored.", + sPath.c_str(), sSong.c_str()); + continue; // skip this #SONG + } + } + else + { + CString sSong = sParams[1]; + new_entry.pSong = SONGMAN->FindSong( sSong ); + + if( new_entry.pSong == NULL ) + { + /* XXX: We need a place to put "user warnings". This is too loud for info.txt-- + * it obscures important warnings--and regular users never look there, anyway. */ + LOG->Trace( "Course file '%s' contains a fixed song entry '%s' that does not exist. " + "This entry will be ignored.", + sPath.c_str(), sSong.c_str()); + continue; // skip this #SONG + } + } + + new_entry.baseDifficulty = StringToDifficulty( sParams[2] ); + if( new_entry.baseDifficulty == DIFFICULTY_INVALID ) + { + int retval = sscanf( sParams[2], "%d..%d", &new_entry.iLowMeter, &new_entry.iHighMeter ); + if( retval == 1 ) + new_entry.iHighMeter = new_entry.iLowMeter; + else if( retval != 2 ) + { + LOG->Warn("Course file '%s' contains an invalid difficulty setting: \"%s\", 3..6 used instead", + sPath.c_str(), sParams[2].c_str()); + new_entry.iLowMeter = 3; + new_entry.iHighMeter = 6; + } + } + + { + /* If "showcourse" or "noshowcourse" is in the list, force new_entry.secret + * on or off. */ + CStringArray mods; + split( sParams[3], ",", mods, true ); + for( int j = (int) mods.size()-1; j >= 0 ; --j ) + { + CString &sMod = mods[j]; + TrimLeft( sMod ); + TrimRight( sMod ); + if( !sMod.CompareNoCase("showcourse") ) + new_entry.bSecret = false; + else if( !sMod.CompareNoCase("noshowcourse") ) + new_entry.bSecret = true; + else if( !sMod.CompareNoCase("nodifficult") ) + new_entry.bNoDifficult = true; + else + continue; + mods.erase(mods.begin() + j); + } + new_entry.sModifiers = join( ",", mods ); + } + + new_entry.attacks = attacks; + new_entry.fGainSeconds = fGainSeconds; + attacks.clear(); + + out.m_vEntries.push_back( new_entry ); + } + else if( bUseCache && !stricmp(sValueName, "RADAR") ) + { + StepsType st = (StepsType) atoi(sParams[1]); + CourseDifficulty cd = (CourseDifficulty) atoi(sParams[2]); + + RadarValues rv; + rv.FromString( sParams[3] ); + out.m_RadarCache[Course::CacheEntry(st, cd)] = rv; + } + else + { + LOG->Warn( "Unexpected value named '%s'", sValueName.c_str() ); + } + } + static TitleSubst tsub("courses"); + + TitleFields title; + title.Title = out.m_sMainTitle; + title.TitleTranslit = out.m_sMainTitleTranslit; + tsub.Subst( title ); + out.m_sMainTitle = title.Title; + out.m_sMainTitleTranslit = title.TitleTranslit; + + /* Cache and load the course banner. Only bother doing this if at least one + * song was found in the course. */ + if( out.m_sBannerPath != "" && !out.m_vEntries.empty() ) + BANNERCACHE->CacheBanner( out.m_sBannerPath ); + + /* Cache each trail RadarValues that's slow to load, so we + * don't have to do it at runtime. */ + if( !bUseCache ) + { + out.CalculateRadarValues(); + + /* If we have any cache data, write the cache file. */ + if( out.m_RadarCache.size() ) + { + CString sCachePath = out.GetCacheFilePath(); + CourseWriterCRS::Write( out, sCachePath, true ); + + SONGINDEX->AddCacheIndex( out.m_sPath, GetHashForFile(out.m_sPath) ); + } + } + + return true; +} + +bool CourseLoaderCRS::LoadEdit( const CString &sEditFilePath, ProfileSlot slot ) +{ + LOG->Trace( "CourseLoaderCRS::LoadEdit(%s)", sEditFilePath.c_str() ); + + int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath ); + if( iBytes > MAX_EDIT_COURSE_SIZE_BYTES ) + { + LOG->Warn( "The edit '%s' is unreasonably large. It won't be loaded.", sEditFilePath.c_str() ); + return false; + } + + Course *pCourse = new Course; + LoadFromCRSFile( sEditFilePath, *pCourse ); + MsdFile msd; + if( !msd.ReadFile( sEditFilePath ) ) + { + LOG->Warn( "Error opening edit file \"%s\": %s", sEditFilePath.c_str(), msd.GetError().c_str() ); + SAFE_DELETE( pCourse ); + return false; + } + + pCourse->m_LoadedFromProfile = slot; + + SONGMAN->AddCourse( pCourse ); + return true; +} + + +/* + * (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. + */ diff --git a/stepmania/src/CourseLoaderCRS.h b/stepmania/src/CourseLoaderCRS.h new file mode 100644 index 0000000000..2920df613c --- /dev/null +++ b/stepmania/src/CourseLoaderCRS.h @@ -0,0 +1,41 @@ +/* CourseLoaderCRS - Reads a Course from an .CRS file. */ + +#ifndef CourseLoaderCRS_H +#define CourseLoaderCRS_H + +#include "GameConstantsAndTypes.h" +class Course; + +class CourseLoaderCRS +{ +public: + static bool LoadFromCRSFile( const CString &sPath, Course &out ); + static bool LoadEdit( const CString &sEditFilePath, ProfileSlot slot ); +}; + +#endif + +/* + * (c) 2001-2004 Chris Danford + * 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. + */ diff --git a/stepmania/src/CourseWriterCRS.cpp b/stepmania/src/CourseWriterCRS.cpp new file mode 100644 index 0000000000..5dc34e4477 --- /dev/null +++ b/stepmania/src/CourseWriterCRS.cpp @@ -0,0 +1,169 @@ +#include "global.h" +#include "CourseWriterCRS.h" +#include "Course.h" +#include "RageFile.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "Song.h" + + +bool CourseWriterCRS::Write( const Course &course, const CString &sPath, bool bSavingCache ) +{ + ASSERT( !course.m_bIsAutogen ); + + RageFile f; + if( !f.Open( sPath, RageFile::WRITE ) ) + { + LOG->Warn( "Could not write course file '%s': %s", sPath.c_str(), f.GetError().c_str() ); + return false; + } + + f.PutLine( ssprintf("#COURSE:%s;", course.m_sMainTitle.c_str()) ); + if( course.m_sMainTitleTranslit != "" ) + f.PutLine( ssprintf("#COURSETRANSLIT:%s;", course.m_sMainTitleTranslit.c_str()) ); + if( course.m_bRepeat ) + f.PutLine( "#REPEAT:YES;" ); + if( course.m_iLives != -1 ) + f.PutLine( ssprintf("#LIVES:%i;", course.m_iLives) ); + + FOREACH_CourseDifficulty( cd ) + { + if( course.m_iCustomMeter[cd] == -1 ) + continue; + f.PutLine( ssprintf("#METER:%s:%i;", CourseDifficultyToString(cd).c_str(), course.m_iCustomMeter[cd]) ); + } + + if( bSavingCache ) + { + f.PutLine( "// cache tags:" ); + + Course::RadarCache_t::const_iterator it; + for( it = course.m_RadarCache.begin(); it != course.m_RadarCache.end(); ++it ) + { + // #RADAR:type:difficulty:value,value,value...; + const Course::CacheEntry &entry = it->first; + StepsType st = entry.first; + CourseDifficulty cd = entry.second; + + CStringArray asRadarValues; + const RadarValues &rv = it->second; + for( int r=0; r < NUM_RADAR_CATEGORIES; r++ ) + asRadarValues.push_back( ssprintf("%.3f", rv[r]) ); + CString sLine = ssprintf( "#RADAR:%i:%i:", st, cd ); + sLine += join( ",", asRadarValues ) + ";"; + f.PutLine( sLine ); + } + f.PutLine( "// end cache tags" ); + } + + for( unsigned i=0; i 0 ) + f.PutLine( ssprintf("#GAINSECONDS:%f;", entry.fGainSeconds) ); + + if( entry.songSort == SongSort_MostPlays && entry.iChooseIndex != -1 ) + { + f.Write( ssprintf( "#SONG:BEST%d", entry.iChooseIndex+1 ) ); + } + else if( entry.songSort == SongSort_FewestPlays && entry.iChooseIndex != -1 ) + { + f.Write( ssprintf( "#SONG:WORST%d", entry.iChooseIndex+1 ) ); + } + else if( entry.pSong ) + { + // strip off everything but the group name and song dir + CStringArray as; + ASSERT( entry.pSong != NULL ); + split( entry.pSong->GetSongDir(), "/", as ); + ASSERT( as.size() >= 2 ); + CString sGroup = as[ as.size()-2 ]; + CString sSong = as[ as.size()-1 ]; + f.Write( "#SONG:" + sGroup + '/' + sSong ); + } + else if( !entry.sSongGroup.empty() ) + { + f.Write( ssprintf( "#SONG:%s/*", entry.sSongGroup.c_str() ) ); + } + else + { + f.Write( "#SONG:*" ); + } + + f.Write( ":" ); + if( entry.baseDifficulty != DIFFICULTY_INVALID ) + f.Write( DifficultyToString(entry.baseDifficulty) ); + else if( entry.iLowMeter != -1 && entry.iHighMeter != -1 ) + f.Write( ssprintf( "%d..%d", entry.iLowMeter, entry.iHighMeter ) ); + f.Write( ":" ); + + CString sModifiers = entry.sModifiers; + bool bDefaultSecret = entry.IsRandomSong(); + if( bDefaultSecret != entry.bSecret ) + { + if( sModifiers != "" ) + sModifiers += ","; + sModifiers += entry.bSecret? "noshowcourse":"showcourse"; + } + + if( entry.bNoDifficult ) + { + if( sModifiers != "" ) + sModifiers += ","; + sModifiers += "nodifficult"; + } + f.Write( sModifiers ); + + f.PutLine( ";" ); + } + + return true; +} + +void CourseWriterCRS::GetEditFile( const Course *pCourse, CString &sOut ) +{ + +} + + +/* + * (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. + */ diff --git a/stepmania/src/CourseWriterCRS.h b/stepmania/src/CourseWriterCRS.h new file mode 100644 index 0000000000..1c9ee300a2 --- /dev/null +++ b/stepmania/src/CourseWriterCRS.h @@ -0,0 +1,40 @@ +/* CourseWriterCRS - Writes a Course to an .CRS file. */ + +#ifndef CourseWriterCRS_H +#define CourseWriterCRS_H + +class Course; + +class CourseWriterCRS +{ +public: + static bool Write( const Course &course, const CString &sPath, bool bSavingCache ); + static void GetEditFile( const Course *pCourse, CString &sOut ); +}; + +#endif + +/* + * (c) 2001-2005 Chris Danford + * 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. + */ diff --git a/stepmania/src/GameCommand.cpp b/stepmania/src/GameCommand.cpp index c3a0fd32cd..f90ef19573 100644 --- a/stepmania/src/GameCommand.cpp +++ b/stepmania/src/GameCommand.cpp @@ -820,7 +820,8 @@ void GameCommand::ApplySelf( const vector &vpns ) const int iNumSuccessful = 0; vector vsEditFiles; - GetDirListing( PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE)+EDIT_SUBDIR+"*.edit", vsEditFiles, false, true ); + GetDirListing( PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE)+EDIT_STEPS_SUBDIR+"*.edit", vsEditFiles, false, true ); + GetDirListing( PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE)+EDIT_COURSES_SUBDIR+"*.crs", vsEditFiles, false, true ); FOREACH_CONST( CString, vsEditFiles, i ) { iNumAttempted++; @@ -966,23 +967,42 @@ void GameCommand::ApplySelf( const vector &vpns ) const bTriedToCopy = true; - CString sFromDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_SUBDIR; - CString sToDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDITS_SUBDIR; - int iNumAttempted = 0; int iNumSuccessful = 0; int iNumOverwritten = 0; - vector vsEditFiles; - GetDirListing( sFromDir+"*.edit", vsEditFiles, false, false ); - FOREACH_CONST( CString, vsEditFiles, i ) { - iNumAttempted++; - if( DoesFileExist(sToDir+*i) ) - iNumOverwritten++; - bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i ); - if( bSuccess ) - iNumSuccessful++; + CString sFromDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_STEPS_SUBDIR; + CString sToDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_STEPS_SUBDIR; + + vector vsFiles; + GetDirListing( sFromDir+"*.edit", vsFiles, false, false ); + FOREACH_CONST( CString, vsFiles, i ) + { + iNumAttempted++; + if( DoesFileExist(sToDir+*i) ) + iNumOverwritten++; + bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i ); + if( bSuccess ) + iNumSuccessful++; + } + } + + { + CString sFromDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_COURSES_SUBDIR; + CString sToDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_COURSES_SUBDIR; + + vector vsFiles; + GetDirListing( sFromDir+"*.crs", vsFiles, false, false ); + FOREACH_CONST( CString, vsFiles, i ) + { + iNumAttempted++; + if( DoesFileExist(sToDir+*i) ) + iNumOverwritten++; + bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i ); + if( bSuccess ) + iNumSuccessful++; + } } MEMCARDMAN->UnmountCard(pn); @@ -1008,23 +1028,42 @@ void GameCommand::ApplySelf( const vector &vpns ) const bTriedToCopy = true; - CString sFromDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDITS_SUBDIR; - CString sToDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_SUBDIR; - int iNumAttempted = 0; int iNumSuccessful = 0; int iNumOverwritten = 0; - vector vsEditFiles; - GetDirListing( sFromDir+"*.edit", vsEditFiles, false, false ); - FOREACH_CONST( CString, vsEditFiles, i ) { - iNumAttempted++; - if( DoesFileExist(sToDir+*i) ) - iNumOverwritten++; - bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i ); - if( bSuccess ) - iNumSuccessful++; + CString sFromDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_STEPS_SUBDIR; + CString sToDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_STEPS_SUBDIR; + + vector vsFiles; + GetDirListing( sFromDir+"*.edit", vsFiles, false, false ); + FOREACH_CONST( CString, vsFiles, i ) + { + iNumAttempted++; + if( DoesFileExist(sToDir+*i) ) + iNumOverwritten++; + bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i ); + if( bSuccess ) + iNumSuccessful++; + } + } + + { + CString sFromDir = MEM_CARD_MOUNT_POINT[pn] + (CString)PREFSMAN->m_sMemoryCardProfileSubdir + "/" + EDIT_COURSES_SUBDIR; + CString sToDir = PROFILEMAN->GetProfileDir(PROFILE_SLOT_MACHINE) + EDIT_COURSES_SUBDIR; + + vector vsFiles; + GetDirListing( sFromDir+"*.crs", vsFiles, false, false ); + FOREACH_CONST( CString, vsFiles, i ) + { + iNumAttempted++; + if( DoesFileExist(sToDir+*i) ) + iNumOverwritten++; + bool bSuccess = FileCopy( sFromDir+*i, sToDir+*i ); + if( bSuccess ) + iNumSuccessful++; + } } MEMCARDMAN->UnmountCard(pn); diff --git a/stepmania/src/NotesLoaderSM.cpp b/stepmania/src/NotesLoaderSM.cpp index 90dc148c7b..92b1ba4c26 100644 --- a/stepmania/src/NotesLoaderSM.cpp +++ b/stepmania/src/NotesLoaderSM.cpp @@ -12,7 +12,7 @@ #include "song.h" #include "Steps.h" -#define MAX_EDIT_SIZE_BYTES 20*1024 // 20 KB +const int MAX_EDIT_STEPS_SIZE_BYTES = 30*1024; // 30KB void SMLoader::LoadFromSMTokens( CString sStepsType, @@ -464,10 +464,10 @@ bool SMLoader::LoadFromDir( CString sPath, Song &out ) bool SMLoader::LoadEdit( CString sEditFilePath, ProfileSlot slot ) { - LOG->Trace( "Song::LoadEdit(%s)", sEditFilePath.c_str() ); + LOG->Trace( "SMLoader::LoadEdit(%s)", sEditFilePath.c_str() ); int iBytes = FILEMAN->GetFileSizeInBytes( sEditFilePath ); - if( iBytes > MAX_EDIT_SIZE_BYTES ) + if( iBytes > MAX_EDIT_STEPS_SIZE_BYTES ) { LOG->Warn( "The edit '%s' is unreasonably large. It won't be loaded.", sEditFilePath.c_str() ); return false; diff --git a/stepmania/src/Profile.cpp b/stepmania/src/Profile.cpp index 234b57381a..06af44d31f 100644 --- a/stepmania/src/Profile.cpp +++ b/stepmania/src/Profile.cpp @@ -26,9 +26,6 @@ #include "GameState.h" #include "Character.h" -// -// Old file versions for backward compatibility -// const CString STATS_XSL = "Stats.xsl"; const CString COMMON_XSL = "Common.xsl"; const CString STATS_XML = "Stats.xml"; @@ -36,7 +33,8 @@ const CString EDITABLE_INI = "Editable.ini"; const CString DONT_SHARE_SIG = "DontShare.sig"; const CString PUBLIC_KEY_FILE = "public.key"; const CString SCREENSHOTS_SUBDIR = "Screenshots/"; -const CString EDITS_SUBDIR = "Edits/"; +const CString EDIT_STEPS_SUBDIR = "Edits/"; +const CString EDIT_COURSES_SUBDIR = "EditCourses/"; const CString LASTGOOD_SUBDIR = "LastGood/"; #define GUID_SIZE_BYTES 8 @@ -834,7 +832,8 @@ bool Profile::SaveAllToDir( CString sDir, bool bSignData ) const SaveStatsWebPageToDir( sDir ); // Empty directories if none exist. - FILEMAN->CreateDir( sDir + EDITS_SUBDIR ); + FILEMAN->CreateDir( sDir + EDIT_STEPS_SUBDIR ); + FILEMAN->CreateDir( sDir + EDIT_COURSES_SUBDIR ); FILEMAN->CreateDir( sDir + SCREENSHOTS_SUBDIR ); return bSaved; diff --git a/stepmania/src/Profile.h b/stepmania/src/Profile.h index 4c0d07f68a..3bc4896ab7 100644 --- a/stepmania/src/Profile.h +++ b/stepmania/src/Profile.h @@ -43,7 +43,8 @@ extern const CString DONT_SHARE_SIG; // The file contains a signature of the STATS_XML's signature. extern const CString PUBLIC_KEY_FILE; extern const CString SCREENSHOTS_SUBDIR; -extern const CString EDITS_SUBDIR; +extern const CString EDIT_STEPS_SUBDIR; +extern const CString EDIT_COURSES_SUBDIR; extern const CString LASTGOOD_SUBDIR; const unsigned int PROFILE_MAX_DISPLAY_NAME_LENGTH = 12; diff --git a/stepmania/src/ProfileManager.cpp b/stepmania/src/ProfileManager.cpp index d398747244..c92b4f77de 100644 --- a/stepmania/src/ProfileManager.cpp +++ b/stepmania/src/ProfileManager.cpp @@ -386,31 +386,8 @@ bool ProfileManager::RenameLocalProfile( CString sProfileID, CString sNewName ) bool ProfileManager::DeleteLocalProfile( CString sProfileID ) { - // delete all files in profile dir CString sProfileDir = LocalProfileIdToDir( sProfileID ); - CStringArray asFilesToDelete; - GetDirListing( sProfileDir + "*", asFilesToDelete, false, true ); - for( unsigned i=0; iRemove( asFilesToDelete[i] ); - - // delete edits - GetDirListing( sProfileDir + EDITS_SUBDIR + "*", asFilesToDelete, false, true ); - for( unsigned i=0; iRemove( asFilesToDelete[i] ); - - // delete lastgood - GetDirListing( sProfileDir + LASTGOOD_SUBDIR + "*", asFilesToDelete, false, true ); - for( unsigned i=0; iRemove( asFilesToDelete[i] ); - - // remove edits dir - FILEMAN->Remove( sProfileDir + "/" + EDITS_SUBDIR ); - - // remove lastgood dir - FILEMAN->Remove( sProfileDir + "/" + LASTGOOD_SUBDIR ); - - // remove profile dir - bool bResult = FILEMAN->Remove( sProfileDir ); + bool bResult = DeleteRecursive( sProfileDir ); RefreshLocalProfilesFromDisk(); return bResult; diff --git a/stepmania/src/RageFileManager.cpp b/stepmania/src/RageFileManager.cpp index baa012ebaf..e410108724 100644 --- a/stepmania/src/RageFileManager.cpp +++ b/stepmania/src/RageFileManager.cpp @@ -5,6 +5,7 @@ #include "RageUtil_FileDB.h" #include "RageLog.h" #include "RageThreads.h" +#include "Foreach.h" #include #if defined(LINUX) @@ -874,6 +875,23 @@ void GetDirListingRecursive( const CString &sDir, const CString &sMatch, CString } } +bool DeleteRecursive( const CString &sDir ) +{ + ASSERT( sDir.Right(1) == "/" ); + + vector vsFiles; + GetDirListing( sDir+"*", vsFiles, false, true ); + FOREACH_CONST( CString, vsFiles, s ) + { + if( IsADirectory(*s) ) + DeleteRecursive( *s ); + else + FILEMAN->Remove( *s ); + } + + return FILEMAN->Remove( sDir ); +} + void FlushDirCache() { FILEMAN->FlushDirCache( "" ); diff --git a/stepmania/src/RageUtil.h b/stepmania/src/RageUtil.h index 218fbf4b29..be69b1ddb2 100644 --- a/stepmania/src/RageUtil.h +++ b/stepmania/src/RageUtil.h @@ -395,6 +395,7 @@ typedef basic_string istring; * declared here since they're used in many places. */ void GetDirListing( const CString &sPath, CStringArray &AddTo, bool bOnlyDirs=false, bool bReturnPathToo=false ); void GetDirListingRecursive( const CString &sDir, const CString &sMatch, CStringArray &AddTo ); /* returns path too */ +bool DeleteRecursive( const CString &sDir ); /* delete the dir and all files/subdirs inside it */ bool DoesFileExist( const CString &sPath ); bool IsAFile( const CString &sPath ); bool IsADirectory( const CString &sPath ); diff --git a/stepmania/src/ScreenEdit.cpp b/stepmania/src/ScreenEdit.cpp index 0f8bfcca3b..fd8cb27a43 100644 --- a/stepmania/src/ScreenEdit.cpp +++ b/stepmania/src/ScreenEdit.cpp @@ -3038,7 +3038,7 @@ void ScreenEdit::SetupCourseAttacks() if( GAMESTATE->m_pCurCourse ) { - GAMESTATE->m_pCurCourse->LoadFromCRSFile( GAMESTATE->m_pCurCourse->m_sPath ); + GAMESTATE->m_pCurCourse->RevertFromDisk(); // Remove this and have a separate reload key? AttackArray Attacks; for( unsigned e = 0; e < GAMESTATE->m_pCurCourse->m_vEntries.size(); ++e ) diff --git a/stepmania/src/SongManager.cpp b/stepmania/src/SongManager.cpp index 552131196e..10860509be 100644 --- a/stepmania/src/SongManager.cpp +++ b/stepmania/src/SongManager.cpp @@ -32,6 +32,8 @@ #include "StatsManager.h" #include "Style.h" #include "BackgroundUtil.h" +#include "Profile.h" +#include "CourseLoaderCRS.h" SongManager* SONGMAN = NULL; // global and accessable from anywhere in our program @@ -39,7 +41,6 @@ const CString SONGS_DIR = "Songs/"; const CString COURSES_DIR = "Courses/"; const int MAX_EDITS_PER_PROFILE = 200; -const int MAX_EDIT_SIZE_BYTES = 30*1024; // 30KB static const ThemeMetric BEGINNER_COLOR ("SongManager","BeginnerColor"); static const ThemeMetric EASY_COLOR ("SongManager","EasyColor"); @@ -386,34 +387,39 @@ RageColor SongManager::GetSongColor( const Song* pSong ) return GetSongGroupColor( pSong->m_sGroupName ); } -CString SongManager::GetCourseGroupBannerPath( CString sCourseGroup ) +CString SongManager::GetCourseGroupBannerPath( const CString &sCourseGroup ) { - for( unsigned i = 0; i < m_sCourseGroupNames.size(); ++i ) + map::const_iterator iter = m_mapCourseGroupToInfo.find( sCourseGroup ); + if( iter == m_mapCourseGroupToInfo.end() ) { - if( sCourseGroup == m_sCourseGroupNames[i] ) - return m_sCourseGroupBannerPaths[i]; + ASSERT_M( 0, ssprintf("requested banner for course group '%s' that doesn't exist",sCourseGroup.c_str()) ); + return ""; + } + else + { + return iter->second.m_sBannerPath; } - - ASSERT_M( 0, ssprintf("requested banner for course group '%s' that doesn't exist",sCourseGroup.c_str()) ); - return ""; } void SongManager::GetCourseGroupNames( CStringArray &AddTo ) { - AddTo.insert(AddTo.end(), m_sCourseGroupNames.begin(), m_sCourseGroupNames.end() ); + FOREACHM_CONST( CString, CourseGroupInfo, m_mapCourseGroupToInfo, iter ) + AddTo.push_back( iter->first ); } -bool SongManager::DoesCourseGroupExist( CString sCourseGroup ) +bool SongManager::DoesCourseGroupExist( const CString &sCourseGroup ) { - return find( m_sCourseGroupNames.begin(), m_sCourseGroupNames.end(), sCourseGroup ) != m_sCourseGroupNames.end(); + return m_mapCourseGroupToInfo.find( sCourseGroup ) != m_mapCourseGroupToInfo.end(); } RageColor SongManager::GetCourseGroupColor( const CString &sCourseGroup ) { - for( unsigned i=0; ifirst == sCourseGroup ) + return SONG_GROUP_COLOR.GetValue( iIndex%NUM_SONG_GROUP_COLORS ); + iIndex++; } ASSERT_M( 0, ssprintf("requested color for course group '%s' that doesn't exist",sCourseGroup.c_str()) ); @@ -476,7 +482,7 @@ int SongManager::GetNumCourses() const int SongManager::GetNumCourseGroups() const { - return m_sCourseGroupNames.size(); + return m_mapCourseGroupToInfo.size(); } CString SongManager::ShortenGroupName( CString sLongGroupName ) @@ -524,7 +530,6 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld ) { LOG->Trace( "Loading courses." ); - m_sCourseGroupNames.clear(); // // Load courses from in Courses dir @@ -535,7 +540,7 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld ) for( unsigned i=0; iLoadFromCRSFile( saCourseFiles[i] ); + CourseLoaderCRS::LoadFromCRSFile( saCourseFiles[i], *pCourse ); m_pCourses.push_back( pCourse ); if( ld ) @@ -545,24 +550,18 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld ) Basename(saCourseFiles[i]).c_str())); ld->Paint(); } - } - if( !saCourseFiles.empty() ) - m_sCourseGroupNames.push_back( "" ); } - // TODO: Search for course group banners if any - FOREACH( CString, m_sCourseGroupNames, s ) - m_sCourseGroupBannerPaths.push_back( "" ); - // Find all group directories in Courses dir { - GetDirListing( COURSES_DIR+"*", m_sCourseGroupNames, true ); - StripCvs( m_sCourseGroupNames ); - SortCStringArray( m_sCourseGroupNames ); + vector vsCourseGroupNames; + GetDirListing( COURSES_DIR+"*", vsCourseGroupNames, true ); + StripCvs( vsCourseGroupNames ); + SortCStringArray( vsCourseGroupNames ); - FOREACH( CString, m_sCourseGroupNames, sCourseGroup ) // for each dir in /Courses/ + FOREACH( CString, vsCourseGroupNames, sCourseGroup ) // for each dir in /Courses/ { // Find all CRS files in this group directory CStringArray vsCoursePaths; @@ -580,11 +579,13 @@ void SongManager::InitCoursesFromDisk( LoadingWindow *ld ) } Course* pCourse = new Course; - pCourse->LoadFromCRSFile( *sCoursePath ); + CourseLoaderCRS::LoadFromCRSFile( *sCoursePath, *pCourse ); m_pCourses.push_back( pCourse ); } } } + + RefreshCourseGroupInfo(); } void SongManager::InitAutogenCourses() @@ -679,7 +680,7 @@ void SongManager::FreeCourses() m_pBestCourses[i][ct].clear(); m_pShuffledCourses.clear(); - m_sCourseGroupNames.clear(); + m_mapCourseGroupToInfo.clear(); } void SongManager::AddCourse( Course *pCourse ) @@ -687,6 +688,7 @@ void SongManager::AddCourse( Course *pCourse ) m_pCourses.push_back( pCourse ); UpdateBest(); UpdateShuffled(); + m_mapCourseGroupToInfo[ pCourse->m_sGroupName ]; // insert } void SongManager::DeleteCourse( Course *pCourse ) @@ -696,6 +698,7 @@ void SongManager::DeleteCourse( Course *pCourse ) m_pCourses.erase( iter ); UpdateBest(); UpdateShuffled(); + RefreshCourseGroupInfo(); } /* Called periodically to wipe out cached NoteData. This is called when we change @@ -927,7 +930,7 @@ bool SongManager::GetExtraStageInfoFromCourse( bool bExtra2, CString sPreferredG return false; Course course; - course.LoadFromCRSFile( sCoursePath ); + CourseLoaderCRS::LoadFromCRSFile( sCoursePath, course ); if( course.GetEstimatedNumStages() <= 0 ) return false; Trail *pTrail = course.GetTrail( GAMESTATE->GetCurrentStyle()->m_StepsType ); @@ -1259,31 +1262,61 @@ void SongManager::UpdateRankingCourses() } } +void SongManager::RefreshCourseGroupInfo() +{ + m_mapCourseGroupToInfo.clear(); + + FOREACH_CONST( Course*, m_pCourses, c ) + { + m_mapCourseGroupToInfo[(*c)->m_sGroupName]; // insert + } + + // TODO: Search for course group banners + FOREACHM( CString, CourseGroupInfo, m_mapCourseGroupToInfo, iter ) + { + } +} + void SongManager::LoadAllFromProfileDir( const CString &sProfileDir, ProfileSlot slot ) { - // - // Load all .edit files. - // - CString sEditsDir = sProfileDir + EDIT_SUBDIR; - - CStringArray asEditsFilesWithPath; - GetDirListing( sEditsDir+"*.edit", asEditsFilesWithPath, false, true ); - - int iNumEditsLoaded = GetNumEditsLoadedFromProfile( slot ); - int size = min( (int) asEditsFilesWithPath.size(), MAX_EDITS_PER_PROFILE - iNumEditsLoaded ); - - for( int i=0; iGetFileSizeInBytes( fn ); - if( iBytes > MAX_EDIT_SIZE_BYTES ) + CStringArray vsFiles; + GetDirListing( sDir+"*.edit", vsFiles, false, true ); + + int iNumEditsLoaded = GetNumEditsLoadedFromProfile( slot ); + int size = min( (int) vsFiles.size(), MAX_EDITS_PER_PROFILE - iNumEditsLoaded ); + + for( int i=0; iWarn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); - continue; - } + CString fn = vsFiles[i]; - SMLoader::LoadEdit( fn, slot ); + SMLoader::LoadEdit( fn, slot ); + } + } + + { + // + // Load all edit courses + // + CString sDir = sProfileDir + EDIT_COURSES_SUBDIR; + + CStringArray vsFiles; + GetDirListing( sDir+"*.crs", vsFiles, false, true ); + + int iNumEditsLoaded = GetNumEditsLoadedFromProfile( slot ); + int size = min( (int) vsFiles.size(), MAX_EDITS_PER_PROFILE - iNumEditsLoaded ); + + for( int i=0; iFreeAllLoadedFromProfile( slot ); + + FOREACH( Course*, m_pCourses, c ) { - Song* pSong = m_pSongs[s]; - pSong->FreeAllLoadedFromProfile( slot ); + if( (*c)->m_LoadedFromProfile == slot ) + this->DeleteCourse( *c ); } // After freeing some Steps pointers, the cache will be invalid. diff --git a/stepmania/src/SongManager.h b/stepmania/src/SongManager.h index dc59303ea7..b964046b18 100644 --- a/stepmania/src/SongManager.h +++ b/stepmania/src/SongManager.h @@ -59,9 +59,9 @@ public: RageColor GetSongColor( const Song* pSong ); RageColor GetDifficultyColor( Difficulty dc ) const; - CString GetCourseGroupBannerPath( CString sCourseGroup ); + CString GetCourseGroupBannerPath( const CString &sCourseGroup ); void GetCourseGroupNames( CStringArray &AddTo ); - bool DoesCourseGroupExist( CString sCourseGroup ); + bool DoesCourseGroupExist( const CString &sCourseGroup ); RageColor GetCourseGroupColor( const CString &sCourseGroupName ); RageColor GetCourseColor( const Course* pCourse ); @@ -104,6 +104,7 @@ public: void SortSongs(); // sort m_pSongs void UpdateRankingCourses(); // courses shown on the ranking screen + void RefreshCourseGroupInfo(); // Lua void PushSelf( lua_State *L ); @@ -131,8 +132,11 @@ protected: vector m_pCourses; vector m_pBestCourses[NUM_PROFILE_SLOTS][NUM_CourseType]; vector m_pShuffledCourses; // used by GetRandomCourse - CStringArray m_sCourseGroupNames; - CStringArray m_sCourseGroupBannerPaths; // each course group may have a banner associated with it + struct CourseGroupInfo + { + CString m_sBannerPath; + }; + map m_mapCourseGroupToInfo; RageTexturePreloader m_TexturePreload; @@ -142,8 +146,6 @@ protected: ThemeMetric1D COURSE_GROUP_COLOR; }; -static const CString EDIT_SUBDIR = "Edits/"; - extern SongManager* SONGMAN; // global and accessable from anywhere in our program #endif