Isolated Endless Course from other courses in GetTrailUnsorted

This should fix stepmania/stepmania#1020 .
This commit is contained in:
blindbox
2016-02-29 11:03:16 +08:00
parent f6fc8e3730
commit 8cc84ebdda
2 changed files with 243 additions and 52 deletions
+241 -52
View File
@@ -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
+2
View File
@@ -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; }