diff --git a/src/Course.cpp b/src/Course.cpp index 43f15dcb0d..6ef6e2dd57 100644 --- a/src/Course.cpp +++ b/src/Course.cpp @@ -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 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 vpSongs; + typedef vector StepsVector; + map 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 &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 &entries, Trail &trail, StepsType &st, + CourseDifficulty &cd, RandomGen &rnd, bool &bCourseDifficultyIsSignificant ) const +{ vector vpAllPossibleSongs; vector vSongAndSteps; vector vpSongs; @@ -452,8 +658,6 @@ bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) map 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 &mappedSongs = mapSongToSteps[resolved.pSong]; + resolved.pSong = vpSongs[ songIndex ]; + const vector &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 &AddTo, StepsType st ) const diff --git a/src/Course.h b/src/Course.h index de3fb0c04d..abd780f8e4 100644 --- a/src/Course.h +++ b/src/Course.h @@ -150,6 +150,8 @@ public: void CalculateRadarValues(); bool GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const; + void GetTrailUnsortedEndless( const vector &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; }