Added additional criteria to StepsCriteria and SongCriteria
Added new m_sSongName property to Song class Slight refactor in Song::LoadSongFromDir() to set m_sSongName Implemented the basics of #SONGSELECT course parameter
This commit is contained in:
+1
-1
@@ -56,7 +56,7 @@ RString CourseEntry::GetTextDescription() const
|
||||
vsEntryDescription.push_back( pSong->GetTranslitFullTitle() );
|
||||
else
|
||||
vsEntryDescription.push_back( "Random" );
|
||||
if( !songCriteria.m_vsGroupNames.size() > 0 )
|
||||
if( songCriteria.m_vsGroupNames.size() > 0 )
|
||||
vsEntryDescription.push_back( join(",", songCriteria.m_vsGroupNames) );
|
||||
if( songCriteria.m_bUseSongGenreAllowedList )
|
||||
vsEntryDescription.push_back( join(",",songCriteria.m_vsSongGenreAllowedList) );
|
||||
|
||||
+200
-22
@@ -128,7 +128,19 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou
|
||||
new_entry.attacks = attacks;
|
||||
new_entry.fGainSeconds = fGainSeconds;
|
||||
attacks.clear();
|
||||
|
||||
out.m_vEntries.push_back( new_entry );
|
||||
}
|
||||
else if( sValueName.EqualsNoCase("SONGSELECT") )
|
||||
{
|
||||
CourseEntry new_entry;
|
||||
if( CourseLoaderCRS::ParseCourseSongSelect(sParams, new_entry, sPath) == false )
|
||||
{
|
||||
out.m_bIncomplete = true;
|
||||
continue; // Skip this #SONGSELECT
|
||||
}
|
||||
new_entry.attacks = attacks;
|
||||
// new_entry.fGainSeconds = fGainSeconds; // SONGSELECT already has a GAINSECONDS option, so we probably don't need this?
|
||||
attacks.clear();
|
||||
out.m_vEntries.push_back( new_entry );
|
||||
}
|
||||
else if( !sValueName.EqualsNoCase("DISPLAYCOURSE") || !sValueName.EqualsNoCase("COMBO") ||
|
||||
@@ -406,27 +418,6 @@ bool CourseLoaderCRS::ParseCourseSong( const MsdFile::value_t &sParams, CourseEn
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_LowestGrades;
|
||||
}
|
||||
// random song within a bpm range
|
||||
else if( sParams[1].Left(strlen("BPMRANGE")) == "BPMRANGE" )
|
||||
{
|
||||
RString sBpmStr = sParams[1].Right(sParams[1].size() - strlen("BPMRANGE"));
|
||||
std::vector<RString> sBpms;
|
||||
split(sBpmStr, "..", sBpms);
|
||||
|
||||
if( sBpms.size() == 2 )
|
||||
{
|
||||
new_entry.songCriteria.m_fMinBPM = strtof(sBpms[0].c_str(), NULL);
|
||||
new_entry.songCriteria.m_fMaxBPM = strtof(sBpms[1].c_str(), NULL);
|
||||
LOG->Trace("BPMRANGE min: %f max %f", new_entry.songCriteria.m_fMinBPM, new_entry.songCriteria.m_fMaxBPM);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->UserLog( "Course file", sPath, "contains an invalid bpm range setting: \"%s\", ignoring",
|
||||
sBpmStr.c_str() );
|
||||
new_entry.songCriteria.m_fMinBPM = -1;
|
||||
new_entry.songCriteria.m_fMaxBPM = -1;
|
||||
}
|
||||
}
|
||||
else if( sParams[1] == "*" )
|
||||
{
|
||||
//new_entry.bSecret = true;
|
||||
@@ -533,6 +524,193 @@ bool CourseLoaderCRS::ParseCourseSong( const MsdFile::value_t &sParams, CourseEn
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CourseLoaderCRS::ParseCourseSongSelect(const MsdFile::value_t &sParams, CourseEntry &new_entry, const RString &sPath)
|
||||
{
|
||||
// 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
|
||||
// #SONGSELECT:TITLE=sometitle,some other title;
|
||||
// #SONGSELECT:GROUP=DDR A,DDR A3;
|
||||
// #SONGSELECT:ARTIST=TaQ,Someone else;
|
||||
// #SONGSELECT:DURATION=69..420;
|
||||
// #SONGSELECT:DIFFICULTY=Easy,Hard;
|
||||
// #SONGSELECT:BPMRANGE=150..160;
|
||||
// #SONGSELECT:METER=5..9;
|
||||
// #SONGSELECT:GENRE=Pop,Techno,Opera;
|
||||
// #SONGSELECT:SORT=Best,1;
|
||||
// #SONGSELECT:SORT=Worst,10;
|
||||
|
||||
for( unsigned i = 1; i < sParams.params.size(); ++i )
|
||||
{
|
||||
std::vector<RString> sParamParts;
|
||||
split(sParams[i], "=", sParamParts);
|
||||
if( sParamParts.size() != 2 )
|
||||
{
|
||||
LOG->UserLog( "Course file", sPath, "has an invalid SONGSELECT sub-parameter, \"%s\"", sParams[i].c_str());
|
||||
return false;
|
||||
}
|
||||
RString sParamName = sParamParts[0];
|
||||
RString sParamValue = sParamParts[1];
|
||||
|
||||
// For params that accept multiple items, if someone were to define it twice in one #SONGSELECT, should we overwrite the first, or append?
|
||||
if( sParamName.EqualsNoCase("TITLE") )
|
||||
{
|
||||
std::vector<RString> songTitles;
|
||||
split(sParamValue, ",", songTitles);
|
||||
new_entry.songCriteria.m_vsSongNames.insert(new_entry.songCriteria.m_vsSongNames.end(), songTitles.begin(), songTitles.end());
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("GROUP") )
|
||||
{
|
||||
std::vector<RString> groups;
|
||||
split(sParamValue, ",", groups);
|
||||
new_entry.songCriteria.m_vsGroupNames.insert(new_entry.songCriteria.m_vsGroupNames.end(), groups.begin(), groups.end());
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("ARTIST") )
|
||||
{
|
||||
std::vector<RString> artists;
|
||||
split(sParamValue, ",", artists);
|
||||
new_entry.songCriteria.m_vsArtistNames.insert(new_entry.songCriteria.m_vsArtistNames.end(), artists.begin(), artists.end());
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("GENRE") )
|
||||
{
|
||||
new_entry.songCriteria.m_bUseSongGenreAllowedList = true;
|
||||
std::vector<RString> genres;
|
||||
split(sParamValue, ",", genres);
|
||||
new_entry.songCriteria.m_vsSongGenreAllowedList.insert(new_entry.songCriteria.m_vsSongGenreAllowedList.end(), genres.begin(), genres.end());
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("DIFFICULTY") )
|
||||
{
|
||||
std::vector<RString> difficultyStrs;
|
||||
std::vector<Difficulty> difficulties;
|
||||
split(sParamValue, ",", difficultyStrs);
|
||||
for (unsigned d = 0; d < difficultyStrs.size(); d++)
|
||||
{
|
||||
Difficulty diff = OldStyleStringToDifficulty(difficultyStrs[d]);
|
||||
// most CRS files use old-style difficulties, but Difficulty enum values can be used in SM5. Test for those too.
|
||||
if (diff == Difficulty_Invalid)
|
||||
{
|
||||
diff = StringToDifficulty(difficultyStrs[d]);
|
||||
}
|
||||
if( diff != Difficulty_Invalid )
|
||||
{
|
||||
difficulties.push_back(diff);
|
||||
}
|
||||
}
|
||||
new_entry.stepsCriteria.m_vDifficulties.insert(new_entry.stepsCriteria.m_vDifficulties.end(), difficulties.begin(), difficulties.end());
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("SORT") )
|
||||
{
|
||||
if( CourseLoaderCRS::ParseCourseSongSort(sParamValue, new_entry, sPath) == false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("DURATION") )
|
||||
{
|
||||
std::vector<RString> durations;
|
||||
split(sParamValue, "..", durations);
|
||||
new_entry.songCriteria.m_fMinDurationSeconds = StringToFloat(durations[0]);
|
||||
new_entry.songCriteria.m_fMaxDurationSeconds = StringToFloat(durations[1]);
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("BPMRANGE") )
|
||||
{
|
||||
std::vector<RString> bpms;
|
||||
split(sParamValue, "..", bpms);
|
||||
new_entry.songCriteria.m_fMinBPM = StringToFloat(bpms[0]);
|
||||
new_entry.songCriteria.m_fMaxBPM = StringToFloat(bpms[1]);
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("METER") )
|
||||
{
|
||||
std::vector<RString> meters;
|
||||
split(sParamValue, "..", meters);
|
||||
new_entry.stepsCriteria.m_iLowMeter = StringToInt(meters[0]);
|
||||
new_entry.stepsCriteria.m_iHighMeter = StringToInt(meters[1]);
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("LIVES") )
|
||||
{
|
||||
new_entry.iGainLives = StringToInt(sParamValue);
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("GAINSECONDS") )
|
||||
{
|
||||
new_entry.fGainSeconds = StringToInt(sParamValue);
|
||||
}
|
||||
else if( sParamName.EqualsNoCase("MODS") )
|
||||
{
|
||||
std::vector<RString> mods;
|
||||
split( sParamValue, ",", mods, true );
|
||||
for( int j = (int) mods.size()-1; j >= 0 ; --j )
|
||||
{
|
||||
RString &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 );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CourseLoaderCRS::ParseCourseSongSort(RString sParam, CourseEntry &new_entry, const RString &sPath)
|
||||
{
|
||||
int iNumSongs = SONGMAN->GetNumSongs();
|
||||
if( sParam.Left(strlen("BEST")) == "BEST" )
|
||||
{
|
||||
int iChooseIndex = StringToInt( sParam.Right(sParam.size()-strlen("BEST")) ) - 1;
|
||||
if( iChooseIndex > iNumSongs )
|
||||
{
|
||||
// looking up a song that doesn't exist.
|
||||
LOG->UserLog( "Course file", sPath, "is trying to load BEST%i with only %i songs installed. "
|
||||
"This entry will be ignored.", iChooseIndex, iNumSongs);
|
||||
return false; // skip this #SONG
|
||||
}
|
||||
|
||||
new_entry.iChooseIndex = iChooseIndex;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_MostPlays;
|
||||
}
|
||||
// least played
|
||||
else if( sParam.Left(strlen("WORST")) == "WORST" )
|
||||
{
|
||||
int iChooseIndex = StringToInt( sParam.Right(sParam.size()-strlen("WORST")) ) - 1;
|
||||
if( iChooseIndex > iNumSongs )
|
||||
{
|
||||
// looking up a song that doesn't exist.
|
||||
LOG->UserLog( "Course file", sPath, "is trying to load WORST%i with only %i songs installed. "
|
||||
"This entry will be ignored.", iChooseIndex, iNumSongs);
|
||||
return false; // skip this #SONG
|
||||
}
|
||||
|
||||
new_entry.iChooseIndex = iChooseIndex;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_FewestPlays;
|
||||
}
|
||||
// best grades
|
||||
else if( sParam.Left(strlen("GRADEBEST")) == "GRADEBEST" )
|
||||
{
|
||||
new_entry.iChooseIndex = StringToInt( sParam.Right(sParam.size()-strlen("GRADEBEST")) ) - 1;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_TopGrades;
|
||||
}
|
||||
// worst grades
|
||||
else if( sParam.Left(strlen("GRADEWORST")) == "GRADEWORST" )
|
||||
{
|
||||
new_entry.iChooseIndex = StringToInt( sParam.Right(sParam.size()-strlen("GRADEWORST")) ) - 1;
|
||||
CLAMP( new_entry.iChooseIndex, 0, 500 );
|
||||
new_entry.songSort = SongSort_LowestGrades;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* (c) 2001-2004 Chris Danford, Glenn Maynard
|
||||
* All rights reserved.
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace CourseLoaderCRS
|
||||
|
||||
bool ParseCourseMods( const MsdFile::value_t &sParams, AttackArray &attacks, const RString &sPath );
|
||||
bool ParseCourseSong( const MsdFile::value_t &sParams, CourseEntry &new_entry, const RString &sPath );
|
||||
bool ParseCourseSongSelect(const MsdFile::value_t &sParams, CourseEntry &new_entry, const RString &sPath);
|
||||
bool ParseCourseSongSort(RString sParam, CourseEntry &new_entry, const RString &sPath);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+5
-10
@@ -295,11 +295,13 @@ bool Song::LoadFromSongDir(RString sDir, bool load_autosave, ProfileSlot from_pr
|
||||
|
||||
bool use_cache = true;
|
||||
|
||||
std::vector<RString> sDirectoryParts;
|
||||
split( m_sSongDir, "/", sDirectoryParts, false );
|
||||
m_sSongName = sDirectoryParts[sDirectoryParts.size() - 2];
|
||||
ASSERT(m_sSongName != "");
|
||||
// save group name
|
||||
if(from_profile == ProfileSlot_Invalid)
|
||||
{
|
||||
std::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 != "" );
|
||||
@@ -1956,15 +1958,8 @@ 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("\\","/");
|
||||
std::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) )
|
||||
if( !sSong.CompareNoCase(m_sSongName) )
|
||||
return true;
|
||||
if( !sSong.CompareNoCase(this->GetTranslitFullTitle()) )
|
||||
return true;
|
||||
|
||||
@@ -171,6 +171,9 @@ public:
|
||||
|
||||
/** @brief The group this Song is in. */
|
||||
RString m_sGroupName;
|
||||
|
||||
/** @brief The base directory name that this Song is in. */
|
||||
RString m_sSongName;
|
||||
|
||||
/**
|
||||
* @brief the Profile this came from.
|
||||
|
||||
+25
-2
@@ -32,7 +32,20 @@ ThemeMetric<bool> SHOW_SECTIONS_IN_LENGTH_SORT ( "MusicWheel", "ShowSectionsInLe
|
||||
|
||||
bool SongCriteria::Matches( const Song *pSong ) const
|
||||
{
|
||||
if( !m_vsGroupNames.size() > 0 && std::find(m_vsGroupNames.begin(), m_vsGroupNames.end(), pSong->m_sGroupName) == m_vsGroupNames.end() )
|
||||
if( m_vsGroupNames.size() > 0 && std::find(m_vsGroupNames.begin(), m_vsGroupNames.end(), pSong->m_sGroupName) == m_vsGroupNames.end() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( m_vsSongNames.size() > 0 && std::find(m_vsSongNames.begin(), m_vsSongNames.end(), pSong->m_sSongName) == m_vsSongNames.end()
|
||||
&& std::find(m_vsSongNames.begin(), m_vsSongNames.end(), pSong->m_sMainTitle) == m_vsSongNames.end()
|
||||
&& std::find(m_vsSongNames.begin(), m_vsSongNames.end(), pSong->m_sMainTitleTranslit) == m_vsSongNames.end() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_vsArtistNames.size() > 0 && std::find(m_vsArtistNames.begin(), m_vsArtistNames.end(), pSong->m_sArtist) == m_vsArtistNames.end()
|
||||
&& std::find(m_vsArtistNames.begin(), m_vsArtistNames.end(), pSong->m_sArtistTranslit) == m_vsArtistNames.end() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -79,7 +92,17 @@ bool SongCriteria::Matches( const Song *pSong ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if( m_fMinDurationSeconds != -1 && pSong->m_fMusicLengthSeconds < m_fMinDurationSeconds )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( m_fMaxDurationSeconds != -1 && pSong->m_fMusicLengthSeconds > m_fMaxDurationSeconds )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch( m_Tutorial )
|
||||
{
|
||||
DEFAULT_FAIL(m_Tutorial);
|
||||
|
||||
+13
-2
@@ -26,6 +26,9 @@ public:
|
||||
*
|
||||
* If an empty string, don't bother using this for searching. */
|
||||
std::vector<RString> m_vsGroupNames;
|
||||
std::vector<RString> m_vsSongNames;
|
||||
std::vector<RString> m_vsArtistNames;
|
||||
|
||||
bool m_bUseSongGenreAllowedList;
|
||||
std::vector<RString> m_vsSongGenreAllowedList;
|
||||
enum Selectable { Selectable_Yes, Selectable_No, Selectable_DontCare } m_Selectable;
|
||||
@@ -35,6 +38,9 @@ public:
|
||||
int m_iMaxStagesForSong; // don't filter if -1
|
||||
float m_fMinBPM; // don't filter if -1
|
||||
float m_fMaxBPM; // don't filter if -1
|
||||
float m_fMinDurationSeconds; // don't filter if -1
|
||||
float m_fMaxDurationSeconds; // don't filter if -1
|
||||
|
||||
/** @brief Is this song used for tutorial purposes? */
|
||||
enum Tutorial
|
||||
{
|
||||
@@ -51,10 +57,11 @@ public:
|
||||
} m_Locked;
|
||||
|
||||
/** @brief Set up some initial song criteria. */
|
||||
SongCriteria(): m_vsGroupNames(), m_bUseSongGenreAllowedList(false),
|
||||
SongCriteria(): m_vsGroupNames(), m_vsSongNames(), m_vsArtistNames(), m_bUseSongGenreAllowedList(false),
|
||||
m_vsSongGenreAllowedList(), m_Selectable(Selectable_DontCare),
|
||||
m_bUseSongAllowedList(false), m_vpSongAllowedList(),
|
||||
m_iMaxStagesForSong(-1), m_fMinBPM(-1), m_fMaxBPM(-1), m_Tutorial(Tutorial_DontCare),
|
||||
m_iMaxStagesForSong(-1), m_fMinBPM(-1), m_fMaxBPM(-1), m_fMinDurationSeconds(-1), m_fMaxDurationSeconds(-1),
|
||||
m_Tutorial(Tutorial_DontCare),
|
||||
m_Locked(Locked_DontCare)
|
||||
{
|
||||
// m_fMinBPM = -1;
|
||||
@@ -78,6 +85,8 @@ public:
|
||||
#define X(x) (x == other.x)
|
||||
return
|
||||
X(m_vsGroupNames) &&
|
||||
X(m_vsSongNames) &&
|
||||
X(m_vsArtistNames) &&
|
||||
X(m_bUseSongGenreAllowedList) &&
|
||||
X(m_vsSongGenreAllowedList) &&
|
||||
X(m_Selectable) &&
|
||||
@@ -86,6 +95,8 @@ public:
|
||||
X(m_iMaxStagesForSong) &&
|
||||
X(m_fMinBPM) &&
|
||||
X(m_fMaxBPM) &&
|
||||
X(m_fMinDurationSeconds) &&
|
||||
X(m_fMaxDurationSeconds) &&
|
||||
X(m_Tutorial) &&
|
||||
X(m_Locked);
|
||||
#undef X
|
||||
|
||||
@@ -18,6 +18,12 @@ bool StepsCriteria::Matches( const Song *pSong, const Steps *pSteps ) const
|
||||
{
|
||||
if( m_difficulty != Difficulty_Invalid && pSteps->GetDifficulty() != m_difficulty )
|
||||
return false;
|
||||
|
||||
if(m_vDifficulties.size() > 0 && std::find(m_vDifficulties.begin(), m_vDifficulties.end(), pSteps->GetDifficulty()) == m_vDifficulties.end() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( m_iLowMeter != -1 && pSteps->GetMeter() < m_iLowMeter )
|
||||
return false;
|
||||
if( m_iHighMeter != -1 && pSteps->GetMeter() > m_iHighMeter )
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
*
|
||||
* Don't filter here if the Difficulty is Difficulty_Invalid. */
|
||||
Difficulty m_difficulty;
|
||||
std::vector<Difficulty> m_vDifficulties;
|
||||
/**
|
||||
* @brief The lowest meter to search for.
|
||||
*
|
||||
@@ -51,6 +52,7 @@ public:
|
||||
|
||||
/** @brief Set up the initial criteria. */
|
||||
StepsCriteria(): m_difficulty(Difficulty_Invalid),
|
||||
m_vDifficulties(),
|
||||
m_iLowMeter(-1), m_iHighMeter(-1),
|
||||
m_st(StepsType_Invalid), m_Locked(Locked_DontCare)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user