bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const { trail.Init(); // XXX: Why are beginner and challenge excluded here? -Wolfman2000 // No idea, probably an obsolete design decision from ITG, removing // exclusion here, but there's some other area that prevents it too. -Kyz /* switch( cd ) { case Difficulty_Beginner: return false; case Difficulty_Challenge: return false; default: break; } */ // Construct a new Trail, add it to the cache, then return it. // Different seed for each course, but the same for the whole round: RandomGen rnd( GAMESTATE->m_iStageSeed + GetHashForString(m_sMainTitle) ); vector<CourseEntry> tmp_entries; if( m_bShuffle ) { /* Always randomize the same way per round. Otherwise, the displayed course * will change every time it's viewed, and the displayed order will have no * bearing on what you'll actually play. */ tmp_entries = m_vEntries; random_shuffle( tmp_entries.begin(), tmp_entries.end(), rnd ); } const vector<CourseEntry> &entries = m_bShuffle ? tmp_entries:m_vEntries; // This can take some time, so don't fill it out unless we need it. vector<Song*> vSongsByMostPlayed; vector<Song*> AllSongsShuffled; trail.m_StepsType = st; trail.m_CourseType = GetCourseType(); trail.m_CourseDifficulty = cd; trail.m_vEntries.reserve(entries.size()); // 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; for (auto e = entries.begin(); e != entries.end(); ++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; std::map<Song*, StepsVector> mapSongToSteps; for (auto &sas: vSongAndSteps) { 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 = Rage::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 = Rage::clamp( dc, Difficulty_Beginner, (Difficulty)(Difficulty_Edit-1) ); } else { new_dc = (Difficulty)(dc + cd - Difficulty_Medium); new_dc = Rage::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 = std::min( std::max( iMinDist, -iLowMeter + 1 ), iMaxDist ); iMaxDist = std::max( std::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; }
bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const { trail.Init(); // XXX: Why are beginner and challenge excluded here? -Wolfman2000 switch( cd ) { case Difficulty_Beginner: return false; case Difficulty_Challenge: return false; default: break; } // Construct a new Trail, add it to the cache, then return it. // Different seed for each course, but the same for the whole round: RandomGen rnd( GAMESTATE->m_iStageSeed + GetHashForString(m_sMainTitle) ); vector<CourseEntry> tmp_entries; if( m_bShuffle ) { /* Always randomize the same way per round. Otherwise, the displayed course * will change every time it's viewed, and the displayed order will have no * bearing on what you'll actually play. */ tmp_entries = m_vEntries; random_shuffle( tmp_entries.begin(), tmp_entries.end(), rnd ); } const vector<CourseEntry> &entries = m_bShuffle ? tmp_entries:m_vEntries; // This can take some time, so don't fill it out unless we need it. vector<Song*> vSongsByMostPlayed; vector<Song*> AllSongsShuffled; trail.m_StepsType = st; trail.m_CourseType = GetCourseType(); trail.m_CourseDifficulty = cd; // Set to true if CourseDifficulty is able to change something. bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium); vector<Song*> vpAllPossibleSongs; vector<SongAndSteps> vSongAndSteps; // Resolve each entry to a Song and Steps. 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 ); }