diff --git a/src/Course.cpp b/src/Course.cpp index f208fc222d..f592023db1 100644 --- a/src/Course.cpp +++ b/src/Course.cpp @@ -34,7 +34,7 @@ static const char *SongSortNames[] = { }; XToString( SongSort ); XToLocalizedString( SongSort ); - +StringToX( SongSort ); /* Maximum lower value of ranges when difficult: */ const int MAX_BOTTOM_RANGE = 10; diff --git a/src/Course.h b/src/Course.h index 17356c9b7a..75e9062688 100644 --- a/src/Course.h +++ b/src/Course.h @@ -39,16 +39,20 @@ enum SongSort SongSort_TopGrades, SongSort_LowestGrades, NUM_SongSort, + SongSort_Invalid, }; /** @brief Loop through the various Song Sorts. */ #define FOREACH_SongSort( i ) FOREACH_ENUM( SongSort, i ) const RString& SongSortToString( SongSort ss ); const RString& SongSortToLocalizedString( SongSort ss ); +SongSort StringToSongSort( const RString& ss ); + class CourseEntry { public: bool bSecret; // show "??????" instead of an exact song + bool bUseSongSelect; // if true, this entry was created from a #SONGSELECT entry, instead of a #SONG entry // filter criteria, applied from top to bottom SongID songID; // don't filter if unset diff --git a/src/CourseLoaderCRS.cpp b/src/CourseLoaderCRS.cpp index 67db202ea0..310b36ff5c 100644 --- a/src/CourseLoaderCRS.cpp +++ b/src/CourseLoaderCRS.cpp @@ -133,7 +133,8 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou else if( sValueName.EqualsNoCase("SONGSELECT") ) { CourseEntry new_entry; - if( CourseLoaderCRS::ParseCourseSongSelect(sParams, new_entry, sPath) == false ) + new_entry.bUseSongSelect = true; + if (CourseLoaderCRS::ParseCourseSongSelect(sParams, new_entry, sPath) == false) { out.m_bIncomplete = true; continue; // Skip this #SONGSELECT @@ -529,7 +530,7 @@ bool CourseLoaderCRS::ParseCourseSongSelect(const MsdFile::value_t &sParams, Cou // I want to be able to make courses that are really weirdly specific, so I'm going to try to put together a different // format for defining the song selection criteria. // The basic idea is to free up the order in which the song criteria need to be specified, and to add a bunch more options. - // TITLE, GROUP, ARTIST, DIFFICULTY, BPMRANGE, DURATION, METER, GENRE, SORT, MODS + // TITLE, GROUP, ARTIST, DIFFICULTY, BPMRANGE, DURATION, METER, GENRE, SORT, MODS, GAINSECONDS, GAINLIVES // #SONGSELECT:TITLE=sometitle,some other title; // #SONGSELECT:GROUP=DDR A,DDR A3; // #SONGSELECT:ARTIST=TaQ,Someone else; @@ -627,7 +628,7 @@ bool CourseLoaderCRS::ParseCourseSongSelect(const MsdFile::value_t &sParams, Cou new_entry.stepsCriteria.m_iLowMeter = StringToInt(meters[0]); new_entry.stepsCriteria.m_iHighMeter = StringToInt(meters[1]); } - else if( sParamName.EqualsNoCase("LIVES") ) + else if( sParamName.EqualsNoCase("GAINLIVES") ) { new_entry.iGainLives = StringToInt(sParamValue); } diff --git a/src/CourseWriterCRS.cpp b/src/CourseWriterCRS.cpp index dc0b3f9361..83c99333b6 100644 --- a/src/CourseWriterCRS.cpp +++ b/src/CourseWriterCRS.cpp @@ -100,99 +100,216 @@ bool CourseWriterCRS::Write( const Course &course, RageFileBasic &f, bool bSavin 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.songSort == SongSort_TopGrades && entry.iChooseIndex != -1 ) - { - f.Write( ssprintf( "#SONG:GRADEBEST%d", entry.iChooseIndex + 1 ) ); - } - else if( entry.songSort == SongSort_LowestGrades && entry.iChooseIndex != -1 ) - { - f.Write( ssprintf( "#SONG:GRADEWORST%d", entry.iChooseIndex + 1 ) ); - } - else if( entry.songID.ToSong() ) - { - Song *pSong = entry.songID.ToSong(); - const RString &sSong = Basename( pSong->GetSongDir() ); - - f.Write( "#SONG:" ); - if( entry.songCriteria.m_vsGroupNames.size() > 0 ) - f.Write( entry.songCriteria.m_vsGroupNames[0] + '/' ); - f.Write( sSong ); - } - else if( entry.songCriteria.m_vsGroupNames.size() > 0 ) - { - f.Write( ssprintf( "#SONG:%s/*", entry.songCriteria.m_vsGroupNames[0].c_str() ) ); + WriteSongSelectCourseEntry(entry, f); } else { - f.Write( "#SONG:*" ); + WriteCourseEntry(entry, f); } - - f.Write( ":" ); - if( entry.stepsCriteria.m_difficulty != Difficulty_Invalid ) - f.Write( DifficultyToString(entry.stepsCriteria.m_difficulty) ); - else if( entry.stepsCriteria.m_iLowMeter != -1 && entry.stepsCriteria.m_iHighMeter != -1 ) - f.Write( ssprintf( "%d..%d", entry.stepsCriteria.m_iLowMeter, entry.stepsCriteria.m_iHighMeter ) ); - f.Write( ":" ); - - RString sModifiers = entry.sModifiers; - - if( entry.bSecret ) - { - if( sModifiers != "" ) - sModifiers += ","; - sModifiers += entry.bSecret? "noshowcourse":"showcourse"; - } - - if( entry.bNoDifficult ) - { - if( sModifiers != "" ) - sModifiers += ","; - sModifiers += "nodifficult"; - } - - if( entry.iGainLives > -1 ) - { - if( !sModifiers.empty() ) - sModifiers += ','; - sModifiers += ssprintf( "award%d", entry.iGainLives ); - } - - f.Write( sModifiers ); - - f.PutLine( ";" ); } return true; } +bool CourseWriterCRS::WriteCourseEntry( const CourseEntry &entry, RageFileBasic &f ) +{ + + for( unsigned j = 0; j < entry.attacks.size(); ++j ) + { + if( j == 0 ) + f.PutLine( "#MODS:" ); + + const Attack &a = entry.attacks[j]; + f.Write( ssprintf( " TIME=%.2f:LEN=%.2f:MODS=%s", + a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) ); + + if( j+1 < entry.attacks.size() ) + f.Write( ":" ); + else + f.Write( ";" ); + f.PutLine( "" ); + } + + if( entry.fGainSeconds > 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.songSort == SongSort_TopGrades && entry.iChooseIndex != -1 ) + { + f.Write( ssprintf( "#SONG:GRADEBEST%d", entry.iChooseIndex + 1 ) ); + } + else if( entry.songSort == SongSort_LowestGrades && entry.iChooseIndex != -1 ) + { + f.Write( ssprintf( "#SONG:GRADEWORST%d", entry.iChooseIndex + 1 ) ); + } + else if( entry.songID.ToSong() ) + { + Song *pSong = entry.songID.ToSong(); + const RString &sSong = Basename( pSong->GetSongDir() ); + + f.Write( "#SONG:" ); + if( entry.songCriteria.m_vsGroupNames.size() > 0 ) + f.Write( entry.songCriteria.m_vsGroupNames[0] + '/' ); + f.Write( sSong ); + } + else if( entry.songCriteria.m_vsGroupNames.size() > 0 ) + { + f.Write( ssprintf( "#SONG:%s/*", entry.songCriteria.m_vsGroupNames[0].c_str() ) ); + } + else + { + f.Write( "#SONG:*" ); + } + + f.Write( ":" ); + if( entry.stepsCriteria.m_difficulty != Difficulty_Invalid ) + f.Write( DifficultyToString(entry.stepsCriteria.m_difficulty) ); + else if( entry.stepsCriteria.m_iLowMeter != -1 && entry.stepsCriteria.m_iHighMeter != -1 ) + f.Write( ssprintf( "%d..%d", entry.stepsCriteria.m_iLowMeter, entry.stepsCriteria.m_iHighMeter ) ); + f.Write( ":" ); + + RString sModifiers = entry.sModifiers; + + if( entry.bSecret ) + { + if( sModifiers != "" ) + sModifiers += ","; + sModifiers += entry.bSecret? "noshowcourse":"showcourse"; + } + + if( entry.bNoDifficult ) + { + if( sModifiers != "" ) + sModifiers += ","; + sModifiers += "nodifficult"; + } + + if( entry.iGainLives > -1 ) + { + if( !sModifiers.empty() ) + sModifiers += ','; + sModifiers += ssprintf( "award%d", entry.iGainLives ); + } + + f.Write( sModifiers ); + + f.PutLine( ";" ); + return true; +} + +bool CourseWriterCRS::WriteSongSelectCourseEntry( const CourseEntry &entry, RageFileBasic &f ) +{ + for( unsigned j = 0; j < entry.attacks.size(); ++j ) + { + if( j == 0 ) + f.PutLine( "#MODS:" ); + + const Attack &a = entry.attacks[j]; + f.Write( ssprintf( " TIME=%.2f:LEN=%.2f:MODS=%s", + a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) ); + + if( j+1 < entry.attacks.size() ) + f.Write( ":" ); + else + f.Write( ";" ); + f.PutLine( "" ); + } + + std::vector songSelectParams; + + if( entry.songCriteria.m_vsSongNames.size() > 0 ) + { + RString songNames = join(",", entry.songCriteria.m_vsSongNames); + songSelectParams.push_back(ssprintf("TITLE=%s", songNames.c_str())); + } + if( entry.songCriteria.m_vsGroupNames.size() > 0) + { + RString groupNames = join(",", entry.songCriteria.m_vsGroupNames); + songSelectParams.push_back(ssprintf("GROUP=%s", groupNames.c_str())); + } + if( entry.songCriteria.m_vsArtistNames.size() > 0) + { + RString artistNames = join(",", entry.songCriteria.m_vsArtistNames); + songSelectParams.push_back(ssprintf("ARTIST=%s", artistNames.c_str())); + } + if( entry.songCriteria.m_bUseSongAllowedList && + entry.songCriteria.m_vsSongGenreAllowedList.size() > 0) + { + RString genreNames = join(",", entry.songCriteria.m_vsSongGenreAllowedList); + songSelectParams.push_back(ssprintf("GENRE=%s", genreNames.c_str())); + } + if( entry.stepsCriteria.m_vDifficulties.size() > 0 ) + { + std::vector difficulties; + for (unsigned d = 0; d < entry.stepsCriteria.m_vDifficulties.size(); d++) + { + Difficulty diff = entry.stepsCriteria.m_vDifficulties[d]; + if( diff != Difficulty_Invalid) + { + difficulties.push_back(DifficultyToString(diff)); + } + } + songSelectParams.push_back("DIFFICULTY=" + join(",", difficulties)); + } + if( entry.songSort != SongSort_Randomize && entry.iChooseIndex > -1) + { + RString songSort = SongSortToString(entry.songSort); + songSelectParams.push_back(ssprintf("SORT=%s=%d", songSort.c_str(), entry.iChooseIndex+1)); + } + if(entry.songCriteria.m_fMinDurationSeconds > 0 + && entry.songCriteria.m_fMaxDurationSeconds > 0 ) + { + songSelectParams.push_back(ssprintf("DURATION=%d..%d", (int)entry.songCriteria.m_fMinDurationSeconds, (int)entry.songCriteria.m_fMaxDurationSeconds)); + } + if(entry.songCriteria.m_fMinBPM > 0 && entry.songCriteria.m_fMaxBPM > 0 ) + { + songSelectParams.push_back(ssprintf("BPMRANGE=%f..%f", entry.songCriteria.m_fMinBPM, entry.songCriteria.m_fMaxBPM)); + } + if(entry.stepsCriteria.m_iLowMeter > 0 && entry.stepsCriteria.m_iHighMeter > 0 ) + { + songSelectParams.push_back(ssprintf("METER=%d..%d", entry.stepsCriteria.m_iLowMeter, entry.stepsCriteria.m_iHighMeter)); + } + if( entry.iGainLives > 0 ) + { + songSelectParams.push_back(ssprintf("GAINLIVES=%d", entry.iGainLives)); + } + if(entry.fGainSeconds > 0 ) + { + songSelectParams.push_back(ssprintf("GAINSECONDS=%f", entry.fGainSeconds)); + } + + std::vector mods; + split(entry.sModifiers, ",", mods); + + if (entry.bSecret) + { + mods.push_back("noshowcourse"); + } + else + { + mods.push_back("showcourse"); + } + if( entry.bNoDifficult ) + { + mods.push_back("nodifficult"); + } + songSelectParams.push_back("MODS=" + join(",", mods)); + + RString songSelect = join(":", songSelectParams); + f.PutLine(ssprintf("#SONGSELECT:%s;", songSelect.c_str())); + + + return true; +} /* * (c) 2001-2004 Chris Danford, Glenn Maynard diff --git a/src/CourseWriterCRS.h b/src/CourseWriterCRS.h index 8a12096658..7974b52502 100644 --- a/src/CourseWriterCRS.h +++ b/src/CourseWriterCRS.h @@ -4,6 +4,7 @@ #define COURSE_WRITER_CRS_H class Course; +class CourseEntry; class RageFileBasic; /** @brief The Course Writer handles writing the .crs files. */ @@ -36,6 +37,10 @@ namespace CourseWriterCRS * @param pCourse the course file. */ void WriteEditFileToMachine( const Course *pCourse ); + + bool WriteCourseEntry( const CourseEntry &entry, RageFileBasic &f ); + + bool WriteSongSelectCourseEntry( const CourseEntry &entry, RageFileBasic &f ); } #endif