Merge pull request #1022 from blindbox/master
Fixes #1020 and add songs that are missing a Medium difficulty to the Endless Course.
This commit is contained in:
+212
-19
@@ -445,22 +445,21 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
|||||||
// Set to true if CourseDifficulty is able to change something.
|
// Set to true if CourseDifficulty is able to change something.
|
||||||
bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium);
|
bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium);
|
||||||
|
|
||||||
vector<Song*> vpAllPossibleSongs;
|
|
||||||
vector<SongAndSteps> vSongAndSteps;
|
|
||||||
vector<Song*> vpSongs;
|
|
||||||
typedef vector<Steps*> StepsVector;
|
|
||||||
map<Song*, StepsVector> mapSongToSteps;
|
|
||||||
int songIndex = 0;
|
|
||||||
bool vpSongsSorted = false;
|
|
||||||
|
|
||||||
// Resolve each entry to a Song and Steps.
|
// Resolve each entry to a Song and Steps.
|
||||||
|
if( trail.m_CourseType == COURSE_TYPE_ENDLESS )
|
||||||
|
{
|
||||||
|
GetTrailUnsortedEndless(entries, trail, st, cd, rnd, bCourseDifficultyIsSignificant);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vector<SongAndSteps> vSongAndSteps;
|
||||||
FOREACH_CONST( CourseEntry, entries, e )
|
FOREACH_CONST( CourseEntry, entries, e )
|
||||||
{
|
{
|
||||||
|
|
||||||
SongAndSteps resolved; // fill this in
|
SongAndSteps resolved; // fill this in
|
||||||
SongCriteria soc = e->songCriteria;
|
SongCriteria soc = e->songCriteria;
|
||||||
|
|
||||||
|
|
||||||
Song *pSong = e->songID.ToSong();
|
Song *pSong = e->songID.ToSong();
|
||||||
if( pSong )
|
if( pSong )
|
||||||
{
|
{
|
||||||
@@ -489,32 +488,38 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
|||||||
StepsUtil::GetAllMatching( soc, stc, vSongAndSteps );
|
StepsUtil::GetAllMatching( soc, stc, vSongAndSteps );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It looks bad to have the same song 2x in a row in a randomly generated course.
|
||||||
|
// Don't allow the same song to be played 2x in a row, unless there's only
|
||||||
|
// one song in vpPossibleSongs.
|
||||||
|
if( trail.m_vEntries.size() > 0 && vSongAndSteps.size() > 1 )
|
||||||
|
{
|
||||||
|
const TrailEntry &teLast = trail.m_vEntries.back();
|
||||||
|
RemoveIf( vSongAndSteps, SongIsEqual( teLast.pSong ) );
|
||||||
|
}
|
||||||
|
|
||||||
// if there are no songs to choose from, abort this CourseEntry
|
// if there are no songs to choose from, abort this CourseEntry
|
||||||
if( vSongAndSteps.empty() )
|
if( vSongAndSteps.empty() )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if( !vpSongsSorted && !vSongAndSteps.empty() ) {
|
vector<Song*> vpSongs;
|
||||||
|
typedef vector<Steps*> StepsVector;
|
||||||
|
map<Song*, StepsVector> mapSongToSteps;
|
||||||
FOREACH_CONST( SongAndSteps, vSongAndSteps, sas )
|
FOREACH_CONST( SongAndSteps, vSongAndSteps, sas )
|
||||||
{
|
{
|
||||||
StepsVector &v = mapSongToSteps[ sas->pSong ];
|
StepsVector &v = mapSongToSteps[ sas->pSong ];
|
||||||
|
|
||||||
v.push_back( sas->pSteps );
|
v.push_back( sas->pSteps );
|
||||||
if( v.size() == 1 )
|
if( v.size() == 1 )
|
||||||
vpSongs.push_back( sas->pSong );
|
vpSongs.push_back( sas->pSong );
|
||||||
}
|
}
|
||||||
vpSongsSorted = true;
|
|
||||||
CourseSortSongs( e->songSort, vpSongs, rnd );
|
CourseSortSongs( e->songSort, vpSongs, rnd );
|
||||||
songIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT( e->iChooseIndex >= 0 );
|
ASSERT( e->iChooseIndex >= 0 );
|
||||||
if( e->iChooseIndex < int( vSongAndSteps.size() ) )
|
if( e->iChooseIndex < int( vSongAndSteps.size() ) )
|
||||||
{
|
{
|
||||||
if( songIndex >= vpSongs.size() ) {
|
resolved.pSong = vpSongs[ e->iChooseIndex ];
|
||||||
songIndex = 0;
|
|
||||||
}
|
|
||||||
resolved.pSong = vpSongs[songIndex];
|
|
||||||
const vector<Steps*> &mappedSongs = mapSongToSteps[ resolved.pSong ];
|
const vector<Steps*> &mappedSongs = mapSongToSteps[ resolved.pSong ];
|
||||||
songIndex++;
|
|
||||||
resolved.pSteps = mappedSongs[ RandomInt( mappedSongs.size() ) ];
|
resolved.pSteps = mappedSongs[ RandomInt( mappedSongs.size() ) ];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -611,7 +616,6 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
|||||||
* This may or may not be the same as e.difficulty. */
|
* This may or may not be the same as e.difficulty. */
|
||||||
te.dc = dc;
|
te.dc = dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
trail.m_vEntries.push_back( te );
|
trail.m_vEntries.push_back( te );
|
||||||
|
|
||||||
// LOG->Trace( "Chose: %s, %d", te.pSong->GetSongDir().c_str(), te.pSteps->GetMeter() );
|
// LOG->Trace( "Chose: %s, %d", te.pSong->GetSongDir().c_str(), te.pSteps->GetMeter() );
|
||||||
@@ -622,6 +626,7 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Hack: If any entry was non-FIXED, or m_bShuffle is set, then radar values
|
/* Hack: If any entry was non-FIXED, or m_bShuffle is set, then radar values
|
||||||
* for this trail will be meaningless as they'll change every time. Pre-cache
|
* for this trail will be meaningless as they'll change every time. Pre-cache
|
||||||
@@ -643,6 +648,194 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
|||||||
return bCourseDifficultyIsSignificant && trail.m_vEntries.size() > 0;
|
return bCourseDifficultyIsSignificant && trail.m_vEntries.size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Course::GetTrailUnsortedEndless( const vector<CourseEntry> &entries, Trail &trail, StepsType &st,
|
||||||
|
CourseDifficulty &cd, RandomGen &rnd, bool &bCourseDifficultyIsSignificant ) const
|
||||||
|
{
|
||||||
|
vector<Song*> vpAllPossibleSongs;
|
||||||
|
vector<SongAndSteps> vSongAndSteps;
|
||||||
|
vector<Song*> vpSongs;
|
||||||
|
typedef vector<Steps*> StepsVector;
|
||||||
|
map<Song*, StepsVector> mapSongToSteps;
|
||||||
|
int songIndex = 0;
|
||||||
|
bool vpSongsSorted = false;
|
||||||
|
FOREACH_CONST( CourseEntry, entries, e )
|
||||||
|
{
|
||||||
|
|
||||||
|
SongAndSteps resolved; // fill this in
|
||||||
|
SongCriteria soc = e->songCriteria;
|
||||||
|
|
||||||
|
|
||||||
|
Song *pSong = e->songID.ToSong();
|
||||||
|
if( pSong )
|
||||||
|
{
|
||||||
|
soc.m_bUseSongAllowedList = true;
|
||||||
|
soc.m_vpSongAllowedList.push_back( pSong );
|
||||||
|
}
|
||||||
|
soc.m_Tutorial = SongCriteria::Tutorial_No;
|
||||||
|
soc.m_Locked = SongCriteria::Locked_Unlocked;
|
||||||
|
if( !soc.m_bUseSongAllowedList )
|
||||||
|
soc.m_iMaxStagesForSong = 1;
|
||||||
|
|
||||||
|
StepsCriteria stc = e->stepsCriteria;
|
||||||
|
stc.m_st = st;
|
||||||
|
stc.m_Locked = StepsCriteria::Locked_Unlocked;
|
||||||
|
|
||||||
|
const bool bSameSongCriteria = e != entries.begin() && ( e - 1 )->songCriteria == soc;
|
||||||
|
const bool bSameStepsCriteria = e != entries.begin() && ( e - 1 )->stepsCriteria == stc;
|
||||||
|
|
||||||
|
if( pSong )
|
||||||
|
{
|
||||||
|
StepsUtil::GetAllMatchingEndless( pSong, stc, vSongAndSteps );
|
||||||
|
}
|
||||||
|
else if( vSongAndSteps.empty() || !( bSameSongCriteria && bSameStepsCriteria ) )
|
||||||
|
{
|
||||||
|
vSongAndSteps.clear();
|
||||||
|
StepsUtil::GetAllMatching( soc, stc, vSongAndSteps );
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no songs to choose from, abort this CourseEntry
|
||||||
|
if( vSongAndSteps.empty() )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if( !vpSongsSorted && !vSongAndSteps.empty() ) {
|
||||||
|
FOREACH_CONST( SongAndSteps, vSongAndSteps, sas )
|
||||||
|
{
|
||||||
|
StepsVector &v = mapSongToSteps[ sas->pSong ];
|
||||||
|
v.push_back( sas->pSteps );
|
||||||
|
if( v.size() == 1 )
|
||||||
|
vpSongs.push_back( sas->pSong );
|
||||||
|
}
|
||||||
|
vpSongsSorted = true;
|
||||||
|
CourseSortSongs( e->songSort, vpSongs, rnd );
|
||||||
|
songIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT( e->iChooseIndex >= 0 );
|
||||||
|
if( e->iChooseIndex < int( vSongAndSteps.size() ) )
|
||||||
|
{
|
||||||
|
if( songIndex >= vpSongs.size() ) {
|
||||||
|
songIndex = 0;
|
||||||
|
}
|
||||||
|
resolved.pSong = vpSongs[ songIndex ];
|
||||||
|
const vector<Steps*> &mappedSongs = mapSongToSteps[ resolved.pSong ];
|
||||||
|
songIndex++;
|
||||||
|
resolved.pSteps = mappedSongs[ RandomInt( mappedSongs.size() ) ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we're not COURSE_DIFFICULTY_REGULAR, then we should be choosing steps that are
|
||||||
|
* either easier or harder than the base difficulty. If no such steps exist, then
|
||||||
|
* just use the one we already have. */
|
||||||
|
Difficulty dc = resolved.pSteps->GetDifficulty();
|
||||||
|
int iLowMeter = e->stepsCriteria.m_iLowMeter;
|
||||||
|
int iHighMeter = e->stepsCriteria.m_iHighMeter;
|
||||||
|
if( cd != Difficulty_Medium && !e->bNoDifficult )
|
||||||
|
{
|
||||||
|
Difficulty new_dc = ( Difficulty )( dc + cd - Difficulty_Medium );
|
||||||
|
if( dc != Difficulty_Medium )
|
||||||
|
{
|
||||||
|
new_dc = cd;
|
||||||
|
}
|
||||||
|
new_dc = clamp( new_dc, ( Difficulty )0, ( Difficulty )( Difficulty_Edit - 1 ) );
|
||||||
|
/*
|
||||||
|
// re-edit this code to work using the metric.
|
||||||
|
Difficulty new_dc;
|
||||||
|
if( INCLUDE_BEGINNER_STEPS )
|
||||||
|
{
|
||||||
|
// don't factor in the course difficulty if we're including
|
||||||
|
// beginner steps -aj
|
||||||
|
new_dc = clamp( dc, Difficulty_Beginner, (Difficulty)(Difficulty_Edit-1) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new_dc = (Difficulty)(dc + cd - Difficulty_Medium);
|
||||||
|
new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(Difficulty_Edit-1) );
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool bChangedDifficulty = false;
|
||||||
|
if( new_dc != dc )
|
||||||
|
{
|
||||||
|
Steps* pNewSteps = SongUtil::GetStepsByDifficulty( resolved.pSong, st, new_dc );
|
||||||
|
if( pNewSteps )
|
||||||
|
{
|
||||||
|
dc = new_dc;
|
||||||
|
resolved.pSteps = pNewSteps;
|
||||||
|
bChangedDifficulty = true;
|
||||||
|
bCourseDifficultyIsSignificant = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hack: We used to adjust low_meter/high_meter above while searching for
|
||||||
|
* songs. However, that results in a different song being chosen for
|
||||||
|
* difficult courses, which is bad when LockCourseDifficulties is disabled;
|
||||||
|
* each player can end up with a different song. Instead, choose based
|
||||||
|
* on the original range, bump the steps based on course difficulty, and
|
||||||
|
* then retroactively tweak the low_meter/high_meter so course displays
|
||||||
|
* line up. */
|
||||||
|
if( e->stepsCriteria.m_difficulty == Difficulty_Invalid && bChangedDifficulty )
|
||||||
|
{
|
||||||
|
/* Minimum and maximum to add to make the meter range contain the actual
|
||||||
|
* meter: */
|
||||||
|
int iMinDist = resolved.pSteps->GetMeter() - iHighMeter;
|
||||||
|
int iMaxDist = resolved.pSteps->GetMeter() - iLowMeter;
|
||||||
|
|
||||||
|
/* Clamp the possible adjustments to try to avoid going under 1 or over
|
||||||
|
* MAX_BOTTOM_RANGE. */
|
||||||
|
iMinDist = min( max( iMinDist, -iLowMeter + 1 ), iMaxDist );
|
||||||
|
iMaxDist = max( min( iMaxDist, MAX_BOTTOM_RANGE - iHighMeter ), iMinDist );
|
||||||
|
|
||||||
|
int iAdd;
|
||||||
|
if( iMaxDist == iMinDist )
|
||||||
|
iAdd = iMaxDist;
|
||||||
|
else
|
||||||
|
iAdd = rnd( iMaxDist - iMinDist ) + iMinDist;
|
||||||
|
iLowMeter += iAdd;
|
||||||
|
iHighMeter += iAdd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TrailEntry te;
|
||||||
|
te.pSong = resolved.pSong;
|
||||||
|
te.pSteps = resolved.pSteps;
|
||||||
|
te.Modifiers = e->sModifiers;
|
||||||
|
te.Attacks = e->attacks;
|
||||||
|
te.bSecret = e->bSecret;
|
||||||
|
te.iLowMeter = iLowMeter;
|
||||||
|
te.iHighMeter = iHighMeter;
|
||||||
|
|
||||||
|
/* If we chose based on meter (not difficulty), then store Difficulty_Invalid, so
|
||||||
|
* other classes can tell that we used meter. */
|
||||||
|
if( e->stepsCriteria.m_difficulty == Difficulty_Invalid )
|
||||||
|
{
|
||||||
|
te.dc = Difficulty_Invalid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Otherwise, store the actual difficulty we got (post-course-difficulty).
|
||||||
|
* This may or may not be the same as e.difficulty. */
|
||||||
|
te.dc = dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
trail.m_vEntries.push_back( te );
|
||||||
|
if( trail.m_vEntries.size() > 0 && te.dc != cd )
|
||||||
|
{
|
||||||
|
trail.m_vEntries.reserve( trail.m_vEntries.size() - 1 );
|
||||||
|
trail.m_vEntries.resize( trail.m_vEntries.size() - 1 );
|
||||||
|
}
|
||||||
|
// LOG->Trace( "Chose: %s, %d", te.pSong->GetSongDir().c_str(), te.pSteps->GetMeter() );
|
||||||
|
|
||||||
|
if( IsAnEdit() && MAX_SONGS_IN_EDIT_COURSE > 0 &&
|
||||||
|
int( trail.m_vEntries.size() ) >= MAX_SONGS_IN_EDIT_COURSE )
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Course::GetTrails( vector<Trail*> &AddTo, StepsType st ) const
|
void Course::GetTrails( vector<Trail*> &AddTo, StepsType st ) const
|
||||||
{
|
{
|
||||||
FOREACH_ShownCourseDifficulty( cd )
|
FOREACH_ShownCourseDifficulty( cd )
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ public:
|
|||||||
void CalculateRadarValues();
|
void CalculateRadarValues();
|
||||||
|
|
||||||
bool GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const;
|
bool GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const;
|
||||||
|
void GetTrailUnsortedEndless( const vector<CourseEntry> &entries, Trail &trail, StepsType &st,
|
||||||
|
CourseDifficulty &cd, RandomGen &rnd, bool &bCourseDifficultyIsSignificant ) const;
|
||||||
bool GetTrailSorted( StepsType st, CourseDifficulty cd, Trail &trail ) const;
|
bool GetTrailSorted( StepsType st, CourseDifficulty cd, Trail &trail ) const;
|
||||||
|
|
||||||
bool IsAnEdit() const { return m_LoadedFromProfile != ProfileSlot_Invalid; }
|
bool IsAnEdit() const { return m_LoadedFromProfile != ProfileSlot_Invalid; }
|
||||||
|
|||||||
@@ -61,6 +61,42 @@ void StepsUtil::GetAllMatching( Song *pSong, const StepsCriteria &stc, vector<So
|
|||||||
out.push_back( SongAndSteps(pSong, *st) );
|
out.push_back( SongAndSteps(pSong, *st) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StepsUtil::GetAllMatchingEndless( Song *pSong, const StepsCriteria &stc, vector<SongAndSteps> &out )
|
||||||
|
{
|
||||||
|
const vector<Steps*> &vSteps = ( stc.m_st == StepsType_Invalid ? pSong->GetAllSteps() :
|
||||||
|
pSong->GetStepsByStepsType( stc.m_st ) );
|
||||||
|
int previousSize = out.size();
|
||||||
|
int successful = false;
|
||||||
|
|
||||||
|
GetAllMatching( pSong, stc, out );
|
||||||
|
if( out.size() != previousSize )
|
||||||
|
{
|
||||||
|
successful = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !successful )
|
||||||
|
{
|
||||||
|
Difficulty difficulty = ( *( vSteps.begin() ) )->GetDifficulty();
|
||||||
|
Difficulty previousDifficulty = difficulty;
|
||||||
|
int lowestDifficultyIndex = 0;
|
||||||
|
vector<Difficulty> difficulties;
|
||||||
|
FOREACH_CONST( Steps*, vSteps, st )
|
||||||
|
{
|
||||||
|
previousDifficulty = difficulty;
|
||||||
|
difficulty = ( *st )->GetDifficulty();
|
||||||
|
if( ( st - vSteps.begin() ) == 0 )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( difficulty < previousDifficulty )
|
||||||
|
{
|
||||||
|
lowestDifficultyIndex = st - vSteps.begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push_back( SongAndSteps( pSong, vSteps.at( lowestDifficultyIndex ) ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool StepsUtil::HasMatching( const SongCriteria &soc, const StepsCriteria &stc )
|
bool StepsUtil::HasMatching( const SongCriteria &soc, const StepsCriteria &stc )
|
||||||
{
|
{
|
||||||
const RString &sGroupName = soc.m_sGroupName.empty()? GROUP_ALL:soc.m_sGroupName;
|
const RString &sGroupName = soc.m_sGroupName.empty()? GROUP_ALL:soc.m_sGroupName;
|
||||||
|
|||||||
@@ -126,6 +126,13 @@ namespace StepsUtil
|
|||||||
* @param out the SongsAndSteps that match.
|
* @param out the SongsAndSteps that match.
|
||||||
*/
|
*/
|
||||||
void GetAllMatching( Song *pSong, const StepsCriteria &stc, vector<SongAndSteps> &out );
|
void GetAllMatching( Song *pSong, const StepsCriteria &stc, vector<SongAndSteps> &out );
|
||||||
|
/**
|
||||||
|
* @brief Retrieve all of the Steps that match the criteria, for Endless mode only.
|
||||||
|
* @param pSong the Song we're checking in.
|
||||||
|
* @param stc the StepsCriteria to look for.
|
||||||
|
* @param out the SongsAndSteps that match.
|
||||||
|
*/
|
||||||
|
void GetAllMatchingEndless( Song *pSong, const StepsCriteria &stc, vector<SongAndSteps> &out );
|
||||||
/**
|
/**
|
||||||
* @brief Is there a <a class="el" href="class_steps.html">Step</a>
|
* @brief Is there a <a class="el" href="class_steps.html">Step</a>
|
||||||
* that matches the criteria?
|
* that matches the criteria?
|
||||||
|
|||||||
Reference in New Issue
Block a user