Isolated Endless Course from other courses in GetTrailUnsorted
This should fix stepmania/stepmania#1020 .
This commit is contained in:
+241
-52
@@ -445,6 +445,212 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
// Set to true if CourseDifficulty is able to change something.
|
||||
bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium);
|
||||
|
||||
|
||||
|
||||
// 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 )
|
||||
{
|
||||
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::GetAllMatching( pSong, stc, vSongAndSteps );
|
||||
}
|
||||
else if( vSongAndSteps.empty() || !( bSameSongCriteria && bSameStepsCriteria ) )
|
||||
{
|
||||
vSongAndSteps.clear();
|
||||
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( vSongAndSteps.empty() )
|
||||
continue;
|
||||
|
||||
vector<Song*> vpSongs;
|
||||
typedef vector<Steps*> StepsVector;
|
||||
map<Song*, StepsVector> mapSongToSteps;
|
||||
FOREACH_CONST( SongAndSteps, vSongAndSteps, sas )
|
||||
{
|
||||
StepsVector &v = mapSongToSteps[ sas->pSong ];
|
||||
|
||||
v.push_back( sas->pSteps );
|
||||
if( v.size() == 1 )
|
||||
vpSongs.push_back( sas->pSong );
|
||||
}
|
||||
|
||||
CourseSortSongs( e->songSort, vpSongs, rnd );
|
||||
|
||||
ASSERT( e->iChooseIndex >= 0 );
|
||||
if( e->iChooseIndex < int( vSongAndSteps.size() ) )
|
||||
{
|
||||
resolved.pSong = vpSongs[ e->iChooseIndex ];
|
||||
const vector<Steps*> &mappedSongs = mapSongToSteps[ resolved.pSong ];
|
||||
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 );
|
||||
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 );
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
* empty data. XXX: How can we do this cleanly, without propagating lots of
|
||||
* otherwise unnecessary data (course entry types, m_bShuffle) to Trail, or
|
||||
* storing a Course pointer in Trail (yuck)? */
|
||||
if( !AllSongsAreFixed() || m_bShuffle )
|
||||
{
|
||||
trail.m_bRadarValuesCached = true;
|
||||
trail.m_CachedRadarValues = RadarValues();
|
||||
}
|
||||
|
||||
/* If we have a manually-entered meter for this difficulty, use it. */
|
||||
if( m_iCustomMeter[cd] != -1 )
|
||||
trail.m_iSpecifiedMeter = m_iCustomMeter[cd];
|
||||
|
||||
/* If the course difficulty never actually changed anything, then this difficulty
|
||||
* is equivalent to Difficulty_Medium; it doesn't exist. */
|
||||
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;
|
||||
@@ -452,8 +658,6 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
map<Song*, StepsVector> mapSongToSteps;
|
||||
int songIndex = 0;
|
||||
bool vpSongsSorted = false;
|
||||
|
||||
// Resolve each entry to a Song and Steps.
|
||||
FOREACH_CONST( CourseEntry, entries, e )
|
||||
{
|
||||
|
||||
@@ -476,14 +680,14 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
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;
|
||||
const bool bSameSongCriteria = e != entries.begin() && ( e - 1 )->songCriteria == soc;
|
||||
const bool bSameStepsCriteria = e != entries.begin() && ( e - 1 )->stepsCriteria == stc;
|
||||
|
||||
if( pSong )
|
||||
{
|
||||
StepsUtil::GetAllMatching( pSong, stc, vSongAndSteps );
|
||||
}
|
||||
else if( vSongAndSteps.empty() || !(bSameSongCriteria && bSameStepsCriteria) )
|
||||
else if( vSongAndSteps.empty() || !( bSameSongCriteria && bSameStepsCriteria ) )
|
||||
{
|
||||
vSongAndSteps.clear();
|
||||
StepsUtil::GetAllMatching( soc, stc, vSongAndSteps );
|
||||
@@ -507,44 +711,48 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
}
|
||||
|
||||
ASSERT( e->iChooseIndex >= 0 );
|
||||
if( e->iChooseIndex < int(vSongAndSteps.size()) )
|
||||
if( e->iChooseIndex < int( vSongAndSteps.size() ) )
|
||||
{
|
||||
if( songIndex >= vpSongs.size() ) {
|
||||
songIndex = 0;
|
||||
}
|
||||
resolved.pSong = vpSongs[songIndex];
|
||||
const vector<Steps*> &mappedSongs = mapSongToSteps[resolved.pSong];
|
||||
resolved.pSong = vpSongs[ songIndex ];
|
||||
const vector<Steps*> &mappedSongs = mapSongToSteps[ resolved.pSong ];
|
||||
songIndex++;
|
||||
resolved.pSteps = mappedSongs[ RandomInt(mappedSongs.size()) ];
|
||||
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. */
|
||||
/* 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);
|
||||
new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(Difficulty_Edit-1) );
|
||||
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) );
|
||||
// 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) );
|
||||
new_dc = (Difficulty)(dc + cd - Difficulty_Medium);
|
||||
new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(Difficulty_Edit-1) );
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -562,29 +770,29 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
* 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: */
|
||||
* 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 );
|
||||
* 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;
|
||||
iAdd = rnd( iMaxDist - iMinDist ) + iMinDist;
|
||||
iLowMeter += iAdd;
|
||||
iHighMeter += iAdd;
|
||||
}
|
||||
@@ -600,7 +808,7 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
te.iHighMeter = iHighMeter;
|
||||
|
||||
/* If we chose based on meter (not difficulty), then store Difficulty_Invalid, so
|
||||
* other classes can tell that we used meter. */
|
||||
* other classes can tell that we used meter. */
|
||||
if( e->stepsCriteria.m_difficulty == Difficulty_Invalid )
|
||||
{
|
||||
te.dc = Difficulty_Invalid;
|
||||
@@ -608,7 +816,7 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
else
|
||||
{
|
||||
/* Otherwise, store the actual difficulty we got (post-course-difficulty).
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -617,30 +825,11 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail )
|
||||
// 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 )
|
||||
int( trail.m_vEntries.size() ) >= MAX_SONGS_IN_EDIT_COURSE )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
* empty data. XXX: How can we do this cleanly, without propagating lots of
|
||||
* otherwise unnecessary data (course entry types, m_bShuffle) to Trail, or
|
||||
* storing a Course pointer in Trail (yuck)? */
|
||||
if( !AllSongsAreFixed() || m_bShuffle )
|
||||
{
|
||||
trail.m_bRadarValuesCached = true;
|
||||
trail.m_CachedRadarValues = RadarValues();
|
||||
}
|
||||
|
||||
/* If we have a manually-entered meter for this difficulty, use it. */
|
||||
if( m_iCustomMeter[cd] != -1 )
|
||||
trail.m_iSpecifiedMeter = m_iCustomMeter[cd];
|
||||
|
||||
/* If the course difficulty never actually changed anything, then this difficulty
|
||||
* is equivalent to Difficulty_Medium; it doesn't exist. */
|
||||
return bCourseDifficultyIsSignificant && trail.m_vEntries.size() > 0;
|
||||
}
|
||||
|
||||
void Course::GetTrails( vector<Trail*> &AddTo, StepsType st ) const
|
||||
|
||||
@@ -150,6 +150,8 @@ public:
|
||||
void CalculateRadarValues();
|
||||
|
||||
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 IsAnEdit() const { return m_LoadedFromProfile != ProfileSlot_Invalid; }
|
||||
|
||||
Reference in New Issue
Block a user