From 7051b2bc162cf497d5c5d8f0790ad17ffb0875da Mon Sep 17 00:00:00 2001 From: AJ Kelly Date: Sun, 8 Jul 2012 10:58:53 -0500 Subject: [PATCH] it's bitching about this for some reason --- src/MusicWheel.cpp | 3304 ++++++++++++++++++++++---------------------- 1 file changed, 1652 insertions(+), 1652 deletions(-) diff --git a/src/MusicWheel.cpp b/src/MusicWheel.cpp index e3f2ffb41a..04714fba25 100644 --- a/src/MusicWheel.cpp +++ b/src/MusicWheel.cpp @@ -1,1652 +1,1652 @@ -#include "global.h" -#include "MusicWheel.h" -#include "RageUtil.h" -#include "SongManager.h" -#include "GameManager.h" -#include "PrefsManager.h" -#include "ScreenManager.h" -#include "RageLog.h" -#include "GameState.h" -#include "ThemeManager.h" -#include "Song.h" -#include "Course.h" -#include "Steps.h" -#include "UnlockManager.h" -#include "GameCommand.h" -#include "ActorUtil.h" -#include "SongUtil.h" -#include "CourseUtil.h" -#include "Foreach.h" -#include "Style.h" -#include "PlayerState.h" -#include "CommonMetrics.h" -#include "MessageManager.h" -#include "LocalizedString.h" - -static Preference g_bMoveRandomToEnd( "MoveRandomToEnd", false ); -static Preference g_bPrecacheAllSorts( "PreCacheAllWheelSorts", false); - -#define NUM_WHEEL_ITEMS ((int)ceil(NUM_WHEEL_ITEMS_TO_DRAW+2)) -#define WHEEL_TEXT(s) THEME->GetString( "MusicWheel", ssprintf("%sText",s.c_str()) ); -#define CUSTOM_ITEM_WHEEL_TEXT(s) THEME->GetString( "MusicWheel", ssprintf("CustomItem%sText",s.c_str()) ); - -static RString SECTION_COLORS_NAME( size_t i ) { return ssprintf("SectionColor%d",int(i+1)); } -static RString CHOICE_NAME( RString s ) { return ssprintf("Choice%s",s.c_str()); } -static RString CUSTOM_WHEEL_ITEM_NAME( RString s ) { return ssprintf("CustomWheelItem%s",s.c_str()); } -static RString CUSTOM_WHEEL_ITEM_COLOR( RString s ) { return ssprintf("%sColor",s.c_str()); } - -static LocalizedString EMPTY_STRING ( "MusicWheel", "Empty" ); - -AutoScreenMessage( SM_SongChanged ); // TODO: Replace this with a Message and MESSAGEMAN -AutoScreenMessage( SM_SortOrderChanging ); -AutoScreenMessage( SM_SortOrderChanged ); - -static SortOrder ForceAppropriateSort( PlayMode pm, SortOrder so ) -{ - switch( pm ) - { - // in course modes, force a particular sort - case PLAY_MODE_ONI: return SORT_ONI_COURSES; - case PLAY_MODE_NONSTOP: return SORT_NONSTOP_COURSES; - case PLAY_MODE_ENDLESS: return SORT_ENDLESS_COURSES; - default: break; - } - - // If we're not in a course mode, don't start in a course sort. - switch( so ) - { - case SORT_ONI_COURSES: - case SORT_NONSTOP_COURSES: - case SORT_ENDLESS_COURSES: - so = SortOrder_Invalid; - default: - return so; - } -} - -MusicWheelItem *MusicWheel::MakeItem() -{ - return new MusicWheelItem; -} - -void MusicWheel::Load( RString sType ) -{ - ROULETTE_SWITCH_SECONDS .Load(sType,"RouletteSwitchSeconds"); - ROULETTE_SLOW_DOWN_SWITCHES .Load(sType,"RouletteSlowDownSwitches"); - NUM_SECTION_COLORS .Load(sType,"NumSectionColors"); - SONG_REAL_EXTRA_COLOR .Load(sType,"SongRealExtraColor"); - SORT_MENU_COLOR .Load(sType,"SortMenuColor"); - SHOW_ROULETTE .Load(sType,"ShowRoulette"); - SHOW_RANDOM .Load(sType,"ShowRandom"); - SHOW_PORTAL .Load(sType,"ShowPortal"); - RANDOM_PICKS_LOCKED_SONGS .Load(sType,"RandomPicksLockedSongs"); - MOST_PLAYED_SONGS_TO_SHOW .Load(sType,"MostPlayedSongsToShow"); - RECENT_SONGS_TO_SHOW .Load(sType,"RecentSongsToShow"); - MODE_MENU_CHOICE_NAMES .Load(sType,"ModeMenuChoiceNames"); - SORT_ORDERS .Load(sType,"SortOrders"); - SHOW_EASY_FLAG .Load(sType,"UseEasyMarkerFlag"); - USE_SECTIONS_WITH_PREFERRED_GROUP .Load(sType,"UseSectionsWithPreferredGroup"); - HIDE_INACTIVE_SECTIONS .Load(sType,"OnlyShowActiveSection"); - REMIND_WHEEL_POSITIONS .Load(sType,"RemindWheelPositions"); - vector vsModeChoiceNames; - split( MODE_MENU_CHOICE_NAMES, ",", vsModeChoiceNames ); - CHOICE .Load(sType,CHOICE_NAME,vsModeChoiceNames); - SECTION_COLORS .Load(sType,SECTION_COLORS_NAME,NUM_SECTION_COLORS); - - CUSTOM_WHEEL_ITEM_NAMES .Load(sType,"CustomWheelItemNames"); - vector vsCustomItemNames; - split( CUSTOM_WHEEL_ITEM_NAMES, ",", vsCustomItemNames ); - CUSTOM_CHOICES.Load(sType,CUSTOM_WHEEL_ITEM_NAME,vsCustomItemNames); - CUSTOM_CHOICE_COLORS.Load(sType,CUSTOM_WHEEL_ITEM_COLOR,vsCustomItemNames); - - ROULETTE_COLOR .Load(sType,"RouletteColor"); - RANDOM_COLOR .Load(sType,"RandomColor"); - PORTAL_COLOR .Load(sType,"PortalColor"); - EMPTY_COLOR .Load(sType,"EmptyColor"); - - WheelBase::Load( sType ); - - SONGMAN->UpdateRankingCourses(); - - m_soundChangeSort.Load( THEME->GetPathS(sType,"sort") ); - m_soundExpand.Load( THEME->GetPathS(sType,"expand"), true ); - m_soundCollapse.Load( THEME->GetPathS(sType,"collapse"), true ); - - // Update for SORT_MOST_PLAYED. - SONGMAN->UpdatePopular(); - - /* Sort SONGMAN's songs by CompareSongPointersByTitle, so we can do other sorts (with - * stable_sort) from its output, and title will be the secondary sort, without having - * to re-sort by title each time. */ - SONGMAN->SortSongs(); - - - FOREACH_ENUM( SortOrder, so ) { - m_WheelItemDatasStatus[so]=INVALID; - } -} - -void MusicWheel::BeginScreen() -{ - RageTimer timer; - RString times; - FOREACH_ENUM( SortOrder, so ) { - if(m_WheelItemDatasStatus[so]!=INVALID) { - m_WheelItemDatasStatus[so]=NEEDREFILTER; - - } - - if(g_bPrecacheAllSorts) { - readyWheelItemsData(so); - times += ssprintf( "%i:%.3f ", so, timer.GetDeltaTime() ); - } - } - if(g_bPrecacheAllSorts) { - LOG->Trace( "MusicWheel sorting took: %s", times.c_str() ); - } - - // Set m_LastModeMenuItem to the first item that matches the current mode. (Do this - // after building wheel item data.) - { - const vector &from = getWheelItemsData(SORT_MODE_MENU); - for( unsigned i=0; im_pAction ); - if( from[i]->m_pAction->DescribesCurrentModeForAllPlayers() ) - { - m_sLastModeMenuItem = from[i]->m_pAction->m_sName; - break; - } - } - } - - WheelBase::BeginScreen(); - - if( GAMESTATE->IsAnExtraStageAndSelectionLocked() ) - { - m_WheelState = STATE_LOCKED; - SCREENMAN->PlayStartSound(); - m_fLockedWheelVelocity = 0; - } - - GAMESTATE->m_SortOrder.Set( GAMESTATE->m_PreferredSortOrder ); - - // Never start in the mode menu; some elements may not initialize correctly. - if( GAMESTATE->m_SortOrder == SORT_MODE_MENU ) - GAMESTATE->m_SortOrder.Set( SortOrder_Invalid ); - - GAMESTATE->m_SortOrder.Set( ForceAppropriateSort(GAMESTATE->m_PlayMode, GAMESTATE->m_SortOrder) ); - - /* Only save the sort order if the player didn't already have one. - * If he did, don't overwrite it. */ - if( GAMESTATE->m_PreferredSortOrder == SortOrder_Invalid ) - GAMESTATE->m_PreferredSortOrder = GAMESTATE->m_SortOrder; - - /* Invalidate current Song if it can't be played - * because there are not enough stages remaining. */ - if( GAMESTATE->m_pCurSong != NULL && - GameState::GetNumStagesMultiplierForSong( GAMESTATE->m_pCurSong ) > GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer() ) - { - GAMESTATE->m_pCurSong.Set( NULL ); - } - - /* Invalidate current Steps if it can't be played - * because there are not enough stages remaining. */ - FOREACH_ENUM( PlayerNumber, p ) - { - if( GAMESTATE->m_pCurSteps[p] != NULL ) - { - vector vpPossibleSteps; - if( GAMESTATE->m_pCurSong != NULL ) - SongUtil::GetPlayableSteps( GAMESTATE->m_pCurSong, vpPossibleSteps ); - bool bStepsIsPossible = find( vpPossibleSteps.begin(), vpPossibleSteps.end(), GAMESTATE->m_pCurSteps[p] ) == vpPossibleSteps.end(); - if( !bStepsIsPossible ) - GAMESTATE->m_pCurSteps[p].Set( NULL ); - } - } - - if(GAMESTATE->m_sPreferredSongGroup != GROUP_ALL) - { - // If a preferred song group is set, open the group and select the - // first song in the group. -aj - if(!GAMESTATE->IsCourseMode()) - { - vector vTemp = SONGMAN->GetSongs(GAMESTATE->m_sPreferredSongGroup); - ASSERT(vTemp.size() > 0); - GAMESTATE->m_pCurSong.Set(vTemp[0]); - }; - SetOpenSection(GAMESTATE->m_sPreferredSongGroup); - SelectSongOrCourse(); - } - else if( !SelectSongOrCourse() ) - { - // Select the the previously selected song (if any) - SetOpenSection(""); - } - - if( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) - { - // store the group song index, run this also here because it forgets the current position when - // not changing the song if you came back from gameplay or your last round song (profiles) - // is not the first one in the group. - for( unsigned idx = 0 ; idx < m_viWheelPositions.size() ; idx++ ) - { - if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(idx) ) - { - m_viWheelPositions[idx] = m_iSelection; - } - } - } - - // rebuild the WheelItems that appear on screen - RebuildWheelItems(); -} - -MusicWheel::~MusicWheel() -{ - FOREACH_ENUM( SortOrder, so ) { - vector::iterator i = m__UnFilteredWheelItemDatas[so].begin(); - vector::iterator iEnd = m__UnFilteredWheelItemDatas[so].end(); - for( ; i != iEnd; ++i ) { - delete *i; - } - - } -} - -/* If a song or course is set in GAMESTATE and available, select it. Otherwise, choose the - * first available song or course. Return true if an item was set, false if no items are - * available. */ -bool MusicWheel::SelectSongOrCourse() -{ - if( GAMESTATE->m_pPreferredSong && SelectSong( GAMESTATE->m_pPreferredSong ) ) - return true; - if( GAMESTATE->m_pCurSong && SelectSong( GAMESTATE->m_pCurSong ) ) - return true; - if( GAMESTATE->m_pPreferredCourse && SelectCourse( GAMESTATE->m_pPreferredCourse ) ) - return true; - if( GAMESTATE->m_pCurCourse && SelectCourse( GAMESTATE->m_pCurCourse ) ) - return true; - - // Select the first selectable song based on the sort order... - vector &wiWheelItems = getWheelItemsData(GAMESTATE->m_SortOrder); - for( unsigned i = 0; i < wiWheelItems.size(); i++ ) - { - if( wiWheelItems[i]->m_pSong ) - return SelectSong( wiWheelItems[i]->m_pSong ); - else if ( wiWheelItems[i]->m_pCourse ) - return SelectCourse( wiWheelItems[i]->m_pCourse ); - } - - LOG->Trace( "MusicWheel::MusicWheel() - No selectable songs or courses found in WheelData" ); - return false; -} - -bool MusicWheel::SelectSection( const RString & SectionName ) -{ - for( unsigned int i = 0; i < m_CurWheelItemData.size(); ++i ) - { - if( m_CurWheelItemData[i]->m_sText == SectionName ) - { - m_iSelection = i; // select it - return true; - } - } - - return false; -} - -bool MusicWheel::SelectSong( const Song *p ) -{ - if( p == NULL ) - return false; - - unsigned i; - vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); - for( i=0; im_pSong == p ) - { - // make its group the currently expanded group - SetOpenSection( from[i]->m_sText ); - break; - } - } - - if( i == from.size() ) - return false; - - for( i=0; im_pSong == p ) - m_iSelection = i; // select it - } - return true; -} - -bool MusicWheel::SelectCourse( const Course *p ) -{ - if( p == NULL ) - return false; - - unsigned i; - vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); - for( i=0; im_pCourse == p ) - { - // make its group the currently expanded group - SetOpenSection( from[i]->m_sText ); - break; - } - } - - if( i == from.size() ) - return false; - - for( i=0; im_pCourse == p ) - m_iSelection = i; // select it - } - - return true; -} - -bool MusicWheel::SelectModeMenuItem() -{ - // Select the last-chosen option. - ASSERT( GAMESTATE->m_SortOrder == SORT_MODE_MENU ); - const vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); - unsigned i; - for( i=0; im_pAction; - if( gc.m_sName == m_sLastModeMenuItem ) - break; - } - if( i == from.size() ) - return false; - - // make its group the currently expanded group - SetOpenSection( from[i]->m_sText ); - - for( i=0; im_pAction->m_sName != m_sLastModeMenuItem ) - continue; - m_iSelection = i; // select it - break; - } - - return true; -} - -// bool MusicWheel::SelectCustomItem() - -void MusicWheel::GetSongList( vector &arraySongs, SortOrder so ) -{ - vector apAllSongs; - switch( so ) - { - case SORT_PREFERRED: - SONGMAN->GetPreferredSortSongs( apAllSongs ); - break; - case SORT_POPULARITY: - apAllSongs = SONGMAN->GetPopularSongs(); - break; - case SORT_GROUP: - // if we're not using sections with a preferred song group, and there - // is a group to load, only load those songs. -aj - if(GAMESTATE->m_sPreferredSongGroup != GROUP_ALL && !USE_SECTIONS_WITH_PREFERRED_GROUP ) - { - apAllSongs = SONGMAN->GetSongs(GAMESTATE->m_sPreferredSongGroup); - break; - } - // otherwise fall through - default: - apAllSongs = SONGMAN->GetAllSongs(); - break; - } - - // filter songs that we don't have enough stages to play - { - vector vTempSongs; - SongCriteria sc; - sc.m_iMaxStagesForSong = GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer(); - SongUtil::FilterSongs( sc, apAllSongs, vTempSongs ); - apAllSongs = vTempSongs; - } - - // copy only songs that have at least one Steps for the current GameMode - for( unsigned i=0; iSongIsLocked( pSong ); - if( iLocked & LOCKED_DISABLED ) - continue; - - // If we're on an extra stage, and this song is selected, ignore #SELECTABLE. - if( pSong != GAMESTATE->m_pCurSong || !GAMESTATE->IsAnExtraStage() ) - { - // Hide songs that asked to be hidden via #SELECTABLE. - if( iLocked & LOCKED_SELECTABLE ) - continue; - if( so != SORT_ROULETTE && iLocked & LOCKED_ROULETTE ) - continue; - } - - /* Hide locked songs. If RANDOM_PICKS_LOCKED_SONGS, hide in Roulette - * and Random, too. */ - if( (so!=SORT_ROULETTE || !RANDOM_PICKS_LOCKED_SONGS) && iLocked ) - continue; - - if( PREFSMAN->m_bOnlyPreferredDifficulties ) - { - // if the song has steps that fit the preferred difficulty of the default player - if( pSong->HasStepsTypeAndDifficulty( GAMESTATE->GetCurrentStyle()->m_StepsType,GAMESTATE->m_PreferredDifficulty[GAMESTATE->GetFirstHumanPlayer()] ) ) - arraySongs.push_back( pSong ); - } - else - { - if(CommonMetrics::AUTO_SET_STYLE) - { - // with AUTO_SET_STYLE on and Autogen off, some songs may get - // hidden. Search through every playable StepsType until you - // find one, then add the song. - // see Issue 147 for more information. -aj - // http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=147 - set vStepsType; - SongUtil::GetPlayableStepsTypes( pSong, vStepsType ); - - FOREACHS( StepsType, vStepsType, st ) - { - if(pSong->HasStepsType(*st)) - { - arraySongs.push_back( pSong ); - break; - } - } - } - else - { - // If the song has at least one steps, add it. - if( pSong->HasStepsType(GAMESTATE->GetCurrentStyle()->m_StepsType) ) - arraySongs.push_back( pSong ); - } - } - } - - /* Hack: Add extra stage item if it was eliminated for any reason - * (eg. it's a long song). */ - if( GAMESTATE->IsAnExtraStage() ) - { - Song* pSong; - Steps* pSteps; - SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pSong, pSteps ); - - if( find( arraySongs.begin(), arraySongs.end(), pSong ) == arraySongs.end() ) - arraySongs.push_back( pSong ); - } -} - -void MusicWheel::BuildWheelItemDatas( vector &arrayWheelItemDatas, SortOrder so ) -{ - switch( so ) - { - case SORT_MODE_MENU: - { - arrayWheelItemDatas.clear(); // clear out the previous wheel items - vector vsNames; - split( MODE_MENU_CHOICE_NAMES, ",", vsNames ); - for( unsigned i=0; i( new GameCommand ); - wid.m_pAction->m_sName = vsNames[i]; - wid.m_pAction->Load( i, ParseCommands(CHOICE.GetValue(vsNames[i])) ); - wid.m_sLabel = WHEEL_TEXT( vsNames[i] ); - - if( !wid.m_pAction->IsPlayable() ) - continue; - - arrayWheelItemDatas.push_back( new MusicWheelItemData(wid) ); - } - break; - } - case SORT_PREFERRED: - case SORT_ROULETTE: - case SORT_GROUP: - case SORT_TITLE: - case SORT_BPM: - case SORT_POPULARITY: - case SORT_TOP_GRADES: - case SORT_ARTIST: - case SORT_GENRE: - case SORT_BEGINNER_METER: - case SORT_EASY_METER: - case SORT_MEDIUM_METER: - case SORT_HARD_METER: - case SORT_CHALLENGE_METER: - case SORT_LENGTH: - case SORT_RECENT: - { - // Make an array of Song*, then sort them - vector arraySongs; - GetSongList( arraySongs, so ); - - bool bUseSections = true; - - // sort the songs - switch( so ) - { - case SORT_PREFERRED: - // obey order specified by the preferred sort list - break; - case SORT_ROULETTE: - { - StepsType st; - Difficulty dc; - SongUtil::GetStepsTypeAndDifficultyFromSortOrder( SORT_EASY_METER, st, dc ); - SongUtil::SortSongPointerArrayByStepsTypeAndMeter( arraySongs, st, dc ); - if( (bool)PREFSMAN->m_bPreferredSortUsesGroups ) - stable_sort( arraySongs.begin(), arraySongs.end(), SongUtil::CompareSongPointersByGroup ); - bUseSections = false; - break; - } - case SORT_GROUP: - SongUtil::SortSongPointerArrayByGroupAndTitle( arraySongs ); - if(USE_SECTIONS_WITH_PREFERRED_GROUP) - bUseSections = true; - else - bUseSections = GAMESTATE->m_sPreferredSongGroup == GROUP_ALL; - break; - case SORT_TITLE: - SongUtil::SortSongPointerArrayByTitle( arraySongs ); - break; - case SORT_BPM: - SongUtil::SortSongPointerArrayByBPM( arraySongs ); - break; - case SORT_POPULARITY: - if( (int) arraySongs.size() > MOST_PLAYED_SONGS_TO_SHOW ) - arraySongs.erase( arraySongs.begin()+MOST_PLAYED_SONGS_TO_SHOW, arraySongs.end() ); - bUseSections = false; - break; - case SORT_TOP_GRADES: - SongUtil::SortSongPointerArrayByGrades( arraySongs, true ); - break; - case SORT_ARTIST: - SongUtil::SortSongPointerArrayByArtist( arraySongs ); - break; - case SORT_GENRE: - SongUtil::SortSongPointerArrayByGenre( arraySongs ); - break; - case SORT_LENGTH: - SongUtil::SortSongPointerArrayByLength( arraySongs ); - break; - case SORT_RECENT: - SongUtil::SortByMostRecentlyPlayedForMachine( arraySongs ); - if( (int) arraySongs.size() > RECENT_SONGS_TO_SHOW ) - arraySongs.erase( arraySongs.begin()+RECENT_SONGS_TO_SHOW, arraySongs.end() ); - bUseSections = false; - break; - case SORT_BEGINNER_METER: - case SORT_EASY_METER: - case SORT_MEDIUM_METER: - case SORT_HARD_METER: - case SORT_CHALLENGE_METER: - case SORT_DOUBLE_EASY_METER: - case SORT_DOUBLE_MEDIUM_METER: - case SORT_DOUBLE_HARD_METER: - case SORT_DOUBLE_CHALLENGE_METER: - StepsType st; - Difficulty dc; - SongUtil::GetStepsTypeAndDifficultyFromSortOrder( so, st, dc ); - SongUtil::SortSongPointerArrayByStepsTypeAndMeter( arraySongs, st, dc ); - break; - default: - FAIL_M("Unhandled sort order! Aborting..."); - } - - // Build an array of WheelItemDatas from the sorted list of Song*'s - arrayWheelItemDatas.clear(); // clear out the previous wheel items - arrayWheelItemDatas.reserve( arraySongs.size() ); - - switch( PREFSMAN->m_MusicWheelUsesSections ) - { - case MusicWheelUsesSections_NEVER: - bUseSections = false; - break; - case MusicWheelUsesSections_ABC_ONLY: - if( so != SORT_TITLE && so != SORT_GROUP ) - bUseSections = false; - break; - default: - break; - } - - if( bUseSections ) - { - // Sorting twice isn't necessary. Instead, modify the compatator - // functions in Song.cpp to have the desired effect. -Chris - /* Keeping groups together with the sorts is tricky and brittle; we - * keep getting OTHER split up without this. However, it puts the - * Grade and BPM sorts in the wrong order, and they're already correct, - * so don't re-sort for them. */ - /* We're using sections, so use the section name as the top-level sort. */ - switch( so ) - { - case SORT_PREFERRED: - case SORT_TOP_GRADES: - case SORT_BPM: - break; // don't sort by section - default: - SongUtil::SortSongPointerArrayBySectionName(arraySongs, so); - break; - } - } - - // make WheelItemDatas with sections - RString sLastSection = ""; - int iSectionColorIndex = 0; - for( unsigned i=0; i< arraySongs.size(); i++ ) - { - Song* pSong = arraySongs[i]; - if( bUseSections ) - { - RString sThisSection = SongUtil::GetSectionNameFromSongAndSort( pSong, so ); - - if( sThisSection != sLastSection ) - { - int iSectionCount = 0; - // Count songs in this section - unsigned j; - for( j=i; j < arraySongs.size(); j++ ) - { - if( SongUtil::GetSectionNameFromSongAndSort( arraySongs[j], so ) != sThisSection ) - break; - } - iSectionCount = j-i; - - // new section, make a section item - // todo: preferred sort section color handling? -aj - RageColor colorSection = (so==SORT_GROUP) ? SONGMAN->GetSongGroupColor(pSong->m_sGroupName) : SECTION_COLORS.GetValue(iSectionColorIndex); - iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS; - // In certain situations (e.g. simulating Pump it Up), themes may - // want to only show one group at a time. - if( !HIDE_INACTIVE_SECTIONS ) - arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, NULL, sThisSection, NULL, colorSection, iSectionCount) ); - sLastSection = sThisSection; - } - } - arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Song, pSong, sLastSection, NULL, SONGMAN->GetSongColor(pSong), 0) ); - } - - if( so != SORT_ROULETTE ) - { - // todo: allow themers to change the order of the items. -aj - if( SHOW_ROULETTE ) - arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Roulette, NULL, "", NULL, ROULETTE_COLOR, 0) ); - - // Only add WheelItemDataType_Random and WheelItemDataType_Portal if there's at least - // one song on the list. - bool bFoundAnySong = false; - for( unsigned i=0; !bFoundAnySong && i < arrayWheelItemDatas.size(); i++ ) - if( arrayWheelItemDatas[i]->m_Type == WheelItemDataType_Song ) - bFoundAnySong = true; - - if( SHOW_RANDOM && bFoundAnySong ) - arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Random, NULL, "", NULL, RANDOM_COLOR, 0) ); - - if( SHOW_PORTAL && bFoundAnySong ) - arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Portal, NULL, "", NULL, PORTAL_COLOR, 0) ); - - // add custom wheel items - vector vsNames; - split( CUSTOM_WHEEL_ITEM_NAMES, ",", vsNames ); - for( unsigned i=0; i( new GameCommand ); - wid.m_pAction->m_sName = vsNames[i]; - wid.m_pAction->Load( i, ParseCommands(CUSTOM_CHOICES.GetValue(vsNames[i])) ); - wid.m_sLabel = CUSTOM_ITEM_WHEEL_TEXT( vsNames[i] ); - - if( !wid.m_pAction->IsPlayable() ) - continue; - - arrayWheelItemDatas.push_back( new MusicWheelItemData(wid) ); - } - } - - if( GAMESTATE->IsAnExtraStageAndSelectionLocked() ) - { - Song* pSong; - Steps* pSteps; - SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pSong, pSteps ); - - for( unsigned i=0; im_pSong == pSong ) - { - // Change the song color. - arrayWheelItemDatas[i]->m_color = SONG_REAL_EXTRA_COLOR; - break; - } - } - } - break; - } - case SORT_ALL_COURSES: - case SORT_NONSTOP_COURSES: - case SORT_ONI_COURSES: - case SORT_ENDLESS_COURSES: - { - bool bOnlyPreferred = PREFSMAN->m_CourseSortOrder == COURSE_SORT_PREFERRED; - - vector vct; - switch( so ) - { - case SORT_NONSTOP_COURSES: - vct.push_back( COURSE_TYPE_NONSTOP ); - break; - case SORT_ONI_COURSES: - vct.push_back( COURSE_TYPE_ONI ); - vct.push_back( COURSE_TYPE_SURVIVAL ); - break; - case SORT_ENDLESS_COURSES: - vct.push_back( COURSE_TYPE_ENDLESS ); - break; - case SORT_ALL_COURSES: - FOREACH_ENUM( CourseType, i ) - vct.push_back( i ); - break; - default: ASSERT(0); break; - } - - vector apCourses; - FOREACH_CONST( CourseType, vct, ct ) - { - if( bOnlyPreferred ) - SONGMAN->GetPreferredSortCourses( *ct, apCourses, PREFSMAN->m_bAutogenGroupCourses ); - else - SONGMAN->GetCourses( *ct, apCourses, PREFSMAN->m_bAutogenGroupCourses ); - } - - switch( PREFSMAN->m_CourseSortOrder ) - { - case COURSE_SORT_SONGS: - CourseUtil::SortCoursePointerArrayByDifficulty( apCourses ); - break; - case COURSE_SORT_PREFERRED: - break; - case COURSE_SORT_METER: - CourseUtil::SortCoursePointerArrayByAvgDifficulty( apCourses ); - break; - case COURSE_SORT_METER_SUM: - CourseUtil::SortCoursePointerArrayByTotalDifficulty( apCourses ); - break; - case COURSE_SORT_RANK: - CourseUtil::SortCoursePointerArrayByRanking( apCourses ); - break; - default: FAIL_M("Impossible to sort the courses! Aborting..."); - } - - // since we can't agree, make it an option - if( PREFSMAN->m_CourseSortOrder != COURSE_SORT_SONGS && g_bMoveRandomToEnd ) - CourseUtil::MoveRandomToEnd( apCourses ); - - if( so == SORT_ALL_COURSES ) - CourseUtil::SortCoursePointerArrayByType( apCourses ); - - arrayWheelItemDatas.clear(); // clear out the previous wheel items - - RString sLastSection = ""; - int iSectionColorIndex = 0; - for( unsigned i=0; iCourseIsLocked(pCourse) ) - continue; - - RString sThisSection = ""; - if( so == SORT_ALL_COURSES ) - { - switch( pCourse->GetPlayMode() ) - { - case PLAY_MODE_ONI: sThisSection = "Oni"; break; - case PLAY_MODE_NONSTOP: sThisSection = "Nonstop"; break; - case PLAY_MODE_ENDLESS: sThisSection = "Endless"; break; - default: break; - } - } - - // check that this course has at least one song playable in the current style - if( !pCourse->IsPlayableIn(GAMESTATE->GetCurrentStyle()->m_StepsType) ) - continue; - - if( sThisSection != sLastSection ) // new section, make a section item - { - RageColor c = SECTION_COLORS.GetValue(iSectionColorIndex); - iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS; - arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, NULL, sThisSection, NULL, c, 0) ); - sLastSection = sThisSection; - } - - RageColor c = ( pCourse->m_sGroupName.size() == 0 ) ? pCourse->GetColor() : SONGMAN->GetCourseColor(pCourse); - arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Course, NULL, sThisSection, pCourse, c, 0) ); - } - break; - } - default: - break; - } - - // init music status icons - for( unsigned i=0; iIsEasy( GAMESTATE->GetCurrentStyle()->m_StepsType ) && SHOW_EASY_FLAG; - WID.m_Flags.bEdits = false; - set vStepsType; - SongUtil::GetPlayableStepsTypes( WID.m_pSong, vStepsType ); - FOREACHS( StepsType, vStepsType, st ) - WID.m_Flags.bEdits |= WID.m_pSong->HasEdits( *st ); - WID.m_Flags.iStagesForSong = GameState::GetNumStagesMultiplierForSong( WID.m_pSong ); - } - else if( WID.m_pCourse != NULL ) - { - WID.m_Flags.bHasBeginnerOr1Meter = false; - WID.m_Flags.bEdits = WID.m_pCourse->IsAnEdit(); - WID.m_Flags.iStagesForSong = 1; - } - } -} - -vector & MusicWheel::getWheelItemsData(SortOrder so) { - // Update the popularity and init icons. - readyWheelItemsData(so); - return m__WheelItemDatas[so]; -} - -void MusicWheel::readyWheelItemsData(SortOrder so) { - if(m_WheelItemDatasStatus[so]!=VALID) { - RageTimer timer; - - vector &aUnFilteredDatas=m__UnFilteredWheelItemDatas[so]; - - if(m_WheelItemDatasStatus[so]==INVALID) { - BuildWheelItemDatas( aUnFilteredDatas, so ); - } - FilterWheelItemDatas( aUnFilteredDatas, m__WheelItemDatas[so], so ); - m_WheelItemDatasStatus[so]=VALID; - - LOG->Trace( "MusicWheel sorting took: %f", timer.GetTimeSinceStart() ); - } - -} - -void MusicWheel::FilterWheelItemDatas(vector &aUnFilteredDatas, vector &aFilteredData, SortOrder so ) -{ - - unsigned unfilteredSize=aUnFilteredDatas.size(); - - /* Only add WheelItemDataType_Portal if there's at least one song on the list. */ - bool bFoundAnySong = false; - for( unsigned i=0; i < unfilteredSize; i++ ) { - if( aUnFilteredDatas[i]->m_Type == WheelItemDataType_Song ) { - bFoundAnySong = true; - break; - } - } - - vector aiRemove; - aiRemove.insert( aiRemove.begin(), unfilteredSize, false ); - - const int iMaxStagesForSong = GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer(); - - Song *pExtraStageSong = NULL; - if( GAMESTATE->IsAnExtraStage() ) - { - Steps *pSteps; - SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pExtraStageSong, pSteps ); - } - - /* Mark any songs that aren't playable in aiRemove. */ - - for( unsigned i=0; i< unfilteredSize; i++ ) - { - MusicWheelItemData& WID = *aUnFilteredDatas[i]; - - /* If we have no songs, remove Random and Portal. */ - if( WID.m_Type == WheelItemDataType_Random || WID.m_Type == WheelItemDataType_Portal ) - { - if( !bFoundAnySong ) - aiRemove[i] = true; - continue; - } - - /* Filter songs that we don't have enough stages to play. */ - if( WID.m_Type == WheelItemDataType_Song ) - { - Song* pSong = WID.m_pSong; - - /* Never remove the extra stage song. */ - if( pExtraStageSong && WID.m_pSong == pExtraStageSong ) - continue; - - /* Check that we have enough stages to play this song. */ - if( GAMESTATE->GetNumStagesMultiplierForSong(WID.m_pSong) > iMaxStagesForSong ) - { - aiRemove[i] = true; - continue; - } - - int iLocked = UNLOCKMAN->SongIsLocked( pSong ); - if( iLocked & LOCKED_DISABLED ) - { - aiRemove[i] = true; - continue; - } - - /* If we're on an extra stage, and this song is selected, ignore #SELECTABLE. */ - if( pSong != GAMESTATE->m_pCurSong || !GAMESTATE->IsAnExtraStage() ) - { - /* Hide songs that asked to be hidden via #SELECTABLE. */ - if( iLocked & LOCKED_SELECTABLE ) - { - aiRemove[i] = true; - continue; - } - if( so != SORT_ROULETTE && iLocked & LOCKED_ROULETTE ) - { - aiRemove[i] = true; - continue; - } - } - - /* Hide locked songs. If RANDOM_PICKS_LOCKED_SONGS, hide in Roulette and Random, - * too. */ - if( (so!=SORT_ROULETTE || !RANDOM_PICKS_LOCKED_SONGS) && iLocked ) - { - aiRemove[i] = true; - continue; - } - - /* If the song has no steps for the current style, remove it. */ - if( !CommonMetrics::AUTO_SET_STYLE && !pSong->HasStepsType(GAMESTATE->GetCurrentStyle()->m_StepsType) ) - { - aiRemove[i] = true; - continue; - } - - // if AutoSetStyle, make sure the song is playable in the end. - if (!SongUtil::IsSongPlayable(pSong)) - { - aiRemove[i] = true; - continue; - } - } - - if( WID.m_Type == WheelItemDataType_Course ) - { - if( !WID.m_pCourse->IsPlayableIn(GAMESTATE->GetCurrentStyle()->m_StepsType) ) - aiRemove[i] = true; - } - } - - /* Filter out the songs we're removing. */ - - aFilteredData.reserve( unfilteredSize ); - for( unsigned i=0; i< unfilteredSize; i++ ) - { - if( aiRemove[i] ) - continue; - aFilteredData.push_back( aUnFilteredDatas[i] ); - } - - // Update the song count in each section header. - unsigned filteredSize=aFilteredData.size(); - for( unsigned i=0; i < filteredSize; ) - { - MusicWheelItemData& WID = *aFilteredData[i]; - ++i; - if( WID.m_Type != WheelItemDataType_Section ) - continue; - - // Count songs in this section - WID.m_iSectionCount = 0; - for( ; i < filteredSize && aFilteredData[i]->m_sText == WID.m_sText; ++i ) - ++WID.m_iSectionCount; - } - - // If we have any section headers with no songs, then we filtered all of the songs in that group, - // so remove it. This isn't optimized like the above since this is a rare case. - for( unsigned i=0; i < filteredSize; ++i ) - { - MusicWheelItemData& WID = *aFilteredData[i]; - if( WID.m_Type != WheelItemDataType_Section ) - continue; - if( WID.m_iSectionCount > 0 ) - continue; - aFilteredData.erase( aFilteredData.begin()+i, aFilteredData.begin()+i+1 ); - --i; - --filteredSize; - } - - /* Update the popularity. This is affected by filtering. */ - if( so == SORT_POPULARITY ) - { - for( unsigned i=0; i< min(3u,aFilteredData.size()); i++ ) - { - MusicWheelItemData& WID = *aFilteredData[i]; - WID.m_Flags.iPlayersBestNumber = i+1; - } - } - - // If we've filtered all items, insert a dummy. - if( aFilteredData.empty() ) - aFilteredData.push_back( new MusicWheelItemData(WheelItemDataType_Section, NULL, EMPTY_STRING, NULL, EMPTY_COLOR, 0) ); -} - -void MusicWheel::UpdateSwitch() -{ - switch( m_WheelState ) - { - case STATE_FLYING_OFF_BEFORE_NEXT_SORT: - { - const Song* pPrevSelectedSong = GetCurWheelItemData(m_iSelection)->m_pSong; - - SCREENMAN->PostMessageToTopScreen( SM_SortOrderChanged, 0 ); - - SetOpenSection( SongUtil::GetSectionNameFromSongAndSort(pPrevSelectedSong, GAMESTATE->m_SortOrder) ); - - m_iSelection = 0; - - // Select the previously selected item - switch( GAMESTATE->m_SortOrder ) - { - default: - // Look for the last selected song or course - SelectSongOrCourse(); - break; - case SORT_MODE_MENU: - SelectModeMenuItem(); - break; - } - - // Change difficulty for sorts by meter - // XXX: do this with GameCommand? - StepsType st; - Difficulty dc; - if( SongUtil::GetStepsTypeAndDifficultyFromSortOrder( GAMESTATE->m_SortOrder, st, dc ) ) - { - ASSERT( dc != Difficulty_Invalid ); - FOREACH_PlayerNumber( p ) - if( GAMESTATE->IsPlayerEnabled(p) ) - GAMESTATE->m_PreferredDifficulty[p].Set( dc ); - } - - SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); - RebuildWheelItems(); - TweenOnScreenForSort(); - } - break; - - case STATE_FLYING_ON_AFTER_NEXT_SORT: - m_WheelState = STATE_SELECTING; // now, wait for input - break; - - case STATE_SELECTING: - m_fTimeLeftInState = 0; - break; - case STATE_ROULETTE_SPINNING: - case STATE_RANDOM_SPINNING: - break; - case STATE_LOCKED: - break; - case STATE_ROULETTE_SLOWING_DOWN: - if( m_iSwitchesLeftInSpinDown == 0 ) - { - m_WheelState = STATE_LOCKED; - m_fTimeLeftInState = 0; - SCREENMAN->PlayStartSound(); - m_fLockedWheelVelocity = 0; - - // Send this again so the screen starts sample music. - SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); - MESSAGEMAN->Broadcast("RouletteStopped"); - } - else - { - --m_iSwitchesLeftInSpinDown; - const float SwitchTimes[] = { 0.5f, 1.3f, 0.8f, 0.4f, 0.2f }; - ASSERT( m_iSwitchesLeftInSpinDown >= 0 && m_iSwitchesLeftInSpinDown <= 4 ); - m_fTimeLeftInState = SwitchTimes[m_iSwitchesLeftInSpinDown]; - m_Moving = 0; - - LOG->Trace( "m_iSwitchesLeftInSpinDown id %d, m_fTimeLeftInState is %f", m_iSwitchesLeftInSpinDown, m_fTimeLeftInState ); - - if( m_iSwitchesLeftInSpinDown == 0 ) - ChangeMusic( randomf(0,1) >= 0.5f? 1:-1 ); - else - ChangeMusic( 1 ); - } - break; - default: - ASSERT(0); // all state changes should be handled explicitly - break; - } -} - -void MusicWheel::ChangeMusic( int iDist ) -{ - m_iSelection += iDist; - wrap( m_iSelection, m_CurWheelItemData.size() ); - - if( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) - { - // store the group song index - for( unsigned idx = 0 ; idx < m_viWheelPositions.size() ; idx++ ) - { - if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(idx) ) - { - m_viWheelPositions[idx] = m_iSelection; - } - } - } - - RebuildWheelItems( iDist ); - - m_fPositionOffsetFromSelection += iDist; - - SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); - - // If we're moving automatically, don't play this; it'll be called in Update. - if(!IsMoving()) - m_soundChangeMusic.Play(); -} - - -bool MusicWheel::ChangeSort( SortOrder new_so, bool allowSameSort ) // return true if change successful -{ - ASSERT( new_so < NUM_SortOrder ); - if( GAMESTATE->m_SortOrder == new_so && !allowSameSort ) - return false; - - // Don't change to SORT_MODE_MENU if it doesn't have at least two choices. - if( new_so == SORT_MODE_MENU && getWheelItemsData(new_so).size() < 2 ) - return false; - - switch( m_WheelState ) - { - case STATE_SELECTING: - case STATE_FLYING_ON_AFTER_NEXT_SORT: - break; // fall through - default: - return false; // don't continue - } - - SCREENMAN->PostMessageToTopScreen( SM_SortOrderChanging, 0 ); - - m_soundChangeSort.Play(); - - TweenOffScreenForSort(); - - // Save the new preference. - if( IsSongSort(new_so) ) - GAMESTATE->m_PreferredSortOrder = new_so; - GAMESTATE->m_SortOrder.Set( new_so ); - - return true; -} - -bool MusicWheel::NextSort() // return true if change successful -{ - // don't allow NextSort when on the mode menu - if( GAMESTATE->m_SortOrder == SORT_MODE_MENU ) - return false; - - vector aSortOrders; - { - Lua *L = LUA->Get(); - SORT_ORDERS.PushSelf( L ); - FOREACH_LUATABLEI( L, -1, i ) - { - SortOrder so = Enum::Check( L, -1, true ); - aSortOrders.push_back( so ); - } - lua_pop( L, 1 ); - LUA->Release(L); - } - - // find the index of the current sort - int cur = 0; - while( cur < int(aSortOrders.size()) && aSortOrders[cur] != GAMESTATE->m_SortOrder ) - ++cur; - - // move to the next sort with wrapping - ++cur; - wrap( cur, aSortOrders.size() ); - - // apply new sort - SortOrder soNew = aSortOrders[cur]; - return ChangeSort( soNew ); -} - -bool MusicWheel::Select() // return true if this selection ends the screen -{ - LOG->Trace( "MusicWheel::Select()" ); - - switch( m_WheelState ) - { - case STATE_FLYING_OFF_BEFORE_NEXT_SORT: - case STATE_ROULETTE_SLOWING_DOWN: - return false; - case STATE_ROULETTE_SPINNING: - m_WheelState = STATE_ROULETTE_SLOWING_DOWN; - m_iSwitchesLeftInSpinDown = ROULETTE_SLOW_DOWN_SWITCHES/2+1 + RandomInt( ROULETTE_SLOW_DOWN_SWITCHES/2 ); - m_fTimeLeftInState = 0.1f; - return false; - case STATE_RANDOM_SPINNING: - m_fPositionOffsetFromSelection = max(m_fPositionOffsetFromSelection, 0.3f); - m_WheelState = STATE_LOCKED; - SCREENMAN->PlayStartSound(); - m_fLockedWheelVelocity = 0; - // Set m_Moving to zero to stop the sounds from playing. - m_Moving = 0; - SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); - return true; - default: break; - } - - if( !WheelBase::Select() ) - return false; - - switch( m_CurWheelItemData[m_iSelection]->m_Type ) - { - case WheelItemDataType_Roulette: - StartRoulette(); - return false; - case WheelItemDataType_Random: - StartRandom(); - return false; - case WheelItemDataType_Sort: - GetCurWheelItemData(m_iSelection)->m_pAction->ApplyToAllPlayers(); - ChangeSort( GAMESTATE->m_PreferredSortOrder ); - m_sLastModeMenuItem = GetCurWheelItemData(m_iSelection)->m_pAction->m_sName; - return false; - case WheelItemDataType_Custom: - GetCurWheelItemData(m_iSelection)->m_pAction->ApplyToAllPlayers(); - if( GetCurWheelItemData(m_iSelection)->m_pAction->m_sScreen != "" ) - return true; - else - return false; - default: return true; - } -} - -void MusicWheel::StartRoulette() -{ - MESSAGEMAN->Broadcast("StartRoulette"); - m_WheelState = STATE_ROULETTE_SPINNING; - m_Moving = 1; - m_TimeBeforeMovingBegins = 0; - m_SpinSpeed = 1.0f/ROULETTE_SWITCH_SECONDS; - GAMESTATE->m_SortOrder.Set( SORT_ROULETTE ); - SetOpenSection( "" ); - RebuildWheelItems(); -} - -void MusicWheel::StartRandom() -{ - MESSAGEMAN->Broadcast("StartRandom"); - /* If RANDOM_PICKS_LOCKED_SONGS is disabled, pick a song from the active sort and - * section. If enabled, picking from the section makes it too easy to trick the - * game into picking a locked song, so pick from SORT_ROULETTE. */ - if( RANDOM_PICKS_LOCKED_SONGS ) - { - // Shuffle and use the roulette wheel. - RandomGen rnd; - random_shuffle( getWheelItemsData(SORT_ROULETTE).begin(), getWheelItemsData(SORT_ROULETTE).end(), rnd ); - GAMESTATE->m_SortOrder.Set( SORT_ROULETTE ); - } - else - { - GAMESTATE->m_SortOrder.Set( GAMESTATE->m_PreferredSortOrder ); - } - SetOpenSection( "" ); - - m_Moving = -1; - m_TimeBeforeMovingBegins = 0; - m_SpinSpeed = 1.0f/ROULETTE_SWITCH_SECONDS; - m_SpinSpeed *= 20.0f; /* faster! */ - m_WheelState = STATE_RANDOM_SPINNING; - - SelectSong( GetPreferredSelectionForRandomOrPortal() ); - - RebuildWheelItems(); -} - -void MusicWheel::SetOpenSection( RString group ) -{ - //LOG->Trace( "SetOpenSection %s", group.c_str() ); - m_sExpandedSectionName = group; - GAMESTATE->sExpandedSectionName = group; - - // wheel positions = num song groups - if ( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) - m_viWheelPositions.resize( SONGMAN->GetNumSongGroups() ); - - const WheelItemBaseData *old = NULL; - if( !m_CurWheelItemData.empty() ) - old = GetCurWheelItemData(m_iSelection); - - vector vpPossibleStyles; - if( CommonMetrics::AUTO_SET_STYLE ) - GAMEMAN->GetCompatibleStyles( GAMESTATE->m_pCurGame, GAMESTATE->GetNumPlayersEnabled(), vpPossibleStyles ); - - m_CurWheelItemData.clear(); - vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); - m_CurWheelItemData.reserve( from.size() ); - for( unsigned i = 0; i < from.size(); ++i ) - { - MusicWheelItemData &d = *from[i]; - if( (d.m_Type == WheelItemDataType_Song || d.m_Type == WheelItemDataType_Course) && !d.m_sText.empty() && - d.m_sText != group ) - continue; - - // If AUTO_SET_STYLE, hide courses that prefer a style that isn't available. - if( d.m_Type == WheelItemDataType_Course && CommonMetrics::AUTO_SET_STYLE ) - { - const Style *pStyle = d.m_pCourse->GetCourseStyle( GAMESTATE->m_pCurGame, GAMESTATE->GetNumSidesJoined() ); - if( pStyle ) - { - if( find( vpPossibleStyles.begin(), vpPossibleStyles.end(), pStyle ) == vpPossibleStyles.end() ) - continue; - } - } - - // Only show tutorial songs in arcade - if( GAMESTATE->m_PlayMode!=PLAY_MODE_REGULAR && - d.m_pSong && - d.m_pSong->IsTutorial() ) - continue; - - m_CurWheelItemData.push_back(&d); - } - - //restore the past group song index - if( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) - { - for( unsigned idx = 0 ; idx < m_viWheelPositions.size() ; idx++ ) - { - if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(idx) ) - { - m_iSelection = m_viWheelPositions[idx]; - } - } - } - else - { - // Try to select the item that was selected before changing groups - m_iSelection = 0; - - for( unsigned i=0; iGetNumSongGroups(); - - for(unsigned i = 0 ; i < iNumGroups ; i++) - { - if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(i) ) - { - if ( i < iNumGroups - 1 ) - return SONGMAN->GetSongGroupByIndex(i+1); - else - { - //i = 0; - return SONGMAN->GetSongGroupByIndex(0); - } - } - } - } - else - { - unsigned int iLastSelection = m_iSelection; - for( unsigned int i = m_iSelection; i < m_CurWheelItemData.size(); ++i ) - { - if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section && i != (unsigned int)m_iSelection ) - { - m_iSelection = i; - return m_CurWheelItemData[i]->m_sText; - } - } - // it should not get down here, but it might happen... only search up to - // the previous selection. - for( unsigned int i = 0; i < iLastSelection; ++i ) - { - if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section && i != (unsigned int)m_iSelection ) - { - m_iSelection = i; - return m_CurWheelItemData[i]->m_sText; - } - } - } - // it shouldn't get here, but just in case... - return ""; -} - -RString MusicWheel::JumpToPrevGroup() -{ - if( HIDE_INACTIVE_SECTIONS ) - { - unsigned iNumGroups = SONGMAN->GetNumSongGroups(); - - for(unsigned i = 0 ; i < iNumGroups ; i++) - { - if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(i) ) - { - if ( i > 0 ) - return SONGMAN->GetSongGroupByIndex(i-1); - else - { - //i = iNumGroups - 1; - return SONGMAN->GetSongGroupByIndex(iNumGroups - 1); - } - } - } - } - else - { - for( unsigned int i = m_iSelection; i > 0; --i ) - { - if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section && i != (unsigned int)m_iSelection ) - { - m_iSelection = i; - return m_CurWheelItemData[i]->m_sText; - } - } - // in case it wasn't found above: - for( unsigned int i = m_CurWheelItemData.size()-1; i > 0; --i ) - { - LOG->Trace( "JumpToPrevGroup iteration 2 | i = %u",i ); - if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section ) - { - m_iSelection = i; - LOG->Trace( "finding it in #2 | i = %u | text = %s",i, m_CurWheelItemData[i]->m_sText.c_str() ); - return m_CurWheelItemData[i]->m_sText; - } - } - } - // it shouldn't get here, but just in case... - return ""; -} - -// Called on late join. Selectable courses may have changed; reopen the section. -void MusicWheel::PlayerJoined() -{ - // TRICKY: If Autogen is off and someone joins, the first player may be on - // a song that has an illegal stepstype for the current amount of players. - // (e.g. a song that only has doubles difficulties and a second player joins.) - // We need to rebuild the wheel item data in this situation. -aj - if( !GAMESTATE->IsCourseMode() && !PREFSMAN->m_bAutogenSteps ) - { - FOREACH_ENUM(SortOrder, so) - { - m_WheelItemDatasStatus[so] = INVALID; - } - RebuildWheelItems(); - } - SetOpenSection( m_sExpandedSectionName ); -} - -bool MusicWheel::IsRouletting() const -{ - return m_WheelState == STATE_ROULETTE_SPINNING || m_WheelState == STATE_ROULETTE_SLOWING_DOWN || - m_WheelState == STATE_RANDOM_SPINNING; -} - -Song* MusicWheel::GetSelectedSong() -{ - switch( m_CurWheelItemData[m_iSelection]->m_Type ) - { - case WheelItemDataType_Portal: - return GetPreferredSelectionForRandomOrPortal(); - default: - return GetCurWheelItemData(m_iSelection)->m_pSong; - } -} - -/* Find a random song. If possible, find one that has the preferred difficulties of - * each player. Prefer songs in the active group, if any. - * - * Note that if this is called, we *must* find a song. We will only be called if - * the active sort has at least one song, but there may be no open group. This means - * that any filters and preferences applied here must be optional. */ -Song *MusicWheel::GetPreferredSelectionForRandomOrPortal() -{ - // probe to find a song that has the preferred - // difficulties of each player - vector vDifficultiesToRequire; - FOREACH_HumanPlayer(p) - { - if( GAMESTATE->m_PreferredDifficulty[p] == Difficulty_Invalid ) - continue; // skip - - // TRICKY: Don't require that edits be present if perferred - // difficulty is Difficulty_Edit. Otherwise, players could use this - // to set up a 100% chance of getting a particular locked song by - // having a single edit for a locked song. - if( GAMESTATE->m_PreferredDifficulty[p] == Difficulty_Edit ) - continue; // skip - - vDifficultiesToRequire.push_back( GAMESTATE->m_PreferredDifficulty[p] ); - } - - RString sPreferredGroup = m_sExpandedSectionName; - vector &wid = getWheelItemsData(GAMESTATE->m_SortOrder); - - StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType; - -#define NUM_PROBES 1000 - for( int i=0; im_Type != WheelItemDataType_Song ) - continue; - - const Song *pSong = wid[iSelection]->m_pSong; - - if( !sPreferredGroup.empty() && wid[iSelection]->m_sText != sPreferredGroup ) - continue; - - // There's an off possibility that somebody might have only one song with only beginner steps. - if( i < 900 && pSong->IsTutorial() ) - continue; - - FOREACH( Difficulty, vDifficultiesToRequire, d ) - if( !pSong->HasStepsTypeAndDifficulty(st,*d) ) - goto try_next; - return wid[iSelection]->m_pSong; -try_next: - ; - } - LOG->Warn( "Couldn't find any songs" ); - return wid[0]->m_pSong; -} - -void MusicWheel::FinishChangingSorts() -{ - FinishTweening(); - m_WheelState = STATE_SELECTING; - m_fTimeLeftInState = 0; -} - -// lua start -#include "LuaBinding.h" - -class LunaMusicWheel: public Luna -{ -public: - static int IsRouletting( T* p, lua_State *L ){ lua_pushboolean( L, p->IsRouletting() ); return 1; } - - LunaMusicWheel() - { - ADD_METHOD( IsRouletting ); - } -}; - -/* - * (c) 2001-2004 Chris Danford, Chris Gomez, Glenn Maynard - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, provided that the above - * copyright notice(s) and this permission notice appear in all copies of - * the Software and that both the above copyright notice(s) and this - * permission notice appear in supporting documentation. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF - * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS - * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT - * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS - * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ +#include "global.h" +#include "MusicWheel.h" +#include "RageUtil.h" +#include "SongManager.h" +#include "GameManager.h" +#include "PrefsManager.h" +#include "ScreenManager.h" +#include "RageLog.h" +#include "GameState.h" +#include "ThemeManager.h" +#include "Song.h" +#include "Course.h" +#include "Steps.h" +#include "UnlockManager.h" +#include "GameCommand.h" +#include "ActorUtil.h" +#include "SongUtil.h" +#include "CourseUtil.h" +#include "Foreach.h" +#include "Style.h" +#include "PlayerState.h" +#include "CommonMetrics.h" +#include "MessageManager.h" +#include "LocalizedString.h" + +static Preference g_bMoveRandomToEnd( "MoveRandomToEnd", false ); +static Preference g_bPrecacheAllSorts( "PreCacheAllWheelSorts", false); + +#define NUM_WHEEL_ITEMS ((int)ceil(NUM_WHEEL_ITEMS_TO_DRAW+2)) +#define WHEEL_TEXT(s) THEME->GetString( "MusicWheel", ssprintf("%sText",s.c_str()) ); +#define CUSTOM_ITEM_WHEEL_TEXT(s) THEME->GetString( "MusicWheel", ssprintf("CustomItem%sText",s.c_str()) ); + +static RString SECTION_COLORS_NAME( size_t i ) { return ssprintf("SectionColor%d",int(i+1)); } +static RString CHOICE_NAME( RString s ) { return ssprintf("Choice%s",s.c_str()); } +static RString CUSTOM_WHEEL_ITEM_NAME( RString s ) { return ssprintf("CustomWheelItem%s",s.c_str()); } +static RString CUSTOM_WHEEL_ITEM_COLOR( RString s ) { return ssprintf("%sColor",s.c_str()); } + +static LocalizedString EMPTY_STRING ( "MusicWheel", "Empty" ); + +AutoScreenMessage( SM_SongChanged ); // TODO: Replace this with a Message and MESSAGEMAN +AutoScreenMessage( SM_SortOrderChanging ); +AutoScreenMessage( SM_SortOrderChanged ); + +static SortOrder ForceAppropriateSort( PlayMode pm, SortOrder so ) +{ + switch( pm ) + { + // in course modes, force a particular sort + case PLAY_MODE_ONI: return SORT_ONI_COURSES; + case PLAY_MODE_NONSTOP: return SORT_NONSTOP_COURSES; + case PLAY_MODE_ENDLESS: return SORT_ENDLESS_COURSES; + default: break; + } + + // If we're not in a course mode, don't start in a course sort. + switch( so ) + { + case SORT_ONI_COURSES: + case SORT_NONSTOP_COURSES: + case SORT_ENDLESS_COURSES: + so = SortOrder_Invalid; + default: + return so; + } +} + +MusicWheelItem *MusicWheel::MakeItem() +{ + return new MusicWheelItem; +} + +void MusicWheel::Load( RString sType ) +{ + ROULETTE_SWITCH_SECONDS .Load(sType,"RouletteSwitchSeconds"); + ROULETTE_SLOW_DOWN_SWITCHES .Load(sType,"RouletteSlowDownSwitches"); + NUM_SECTION_COLORS .Load(sType,"NumSectionColors"); + SONG_REAL_EXTRA_COLOR .Load(sType,"SongRealExtraColor"); + SORT_MENU_COLOR .Load(sType,"SortMenuColor"); + SHOW_ROULETTE .Load(sType,"ShowRoulette"); + SHOW_RANDOM .Load(sType,"ShowRandom"); + SHOW_PORTAL .Load(sType,"ShowPortal"); + RANDOM_PICKS_LOCKED_SONGS .Load(sType,"RandomPicksLockedSongs"); + MOST_PLAYED_SONGS_TO_SHOW .Load(sType,"MostPlayedSongsToShow"); + RECENT_SONGS_TO_SHOW .Load(sType,"RecentSongsToShow"); + MODE_MENU_CHOICE_NAMES .Load(sType,"ModeMenuChoiceNames"); + SORT_ORDERS .Load(sType,"SortOrders"); + SHOW_EASY_FLAG .Load(sType,"UseEasyMarkerFlag"); + USE_SECTIONS_WITH_PREFERRED_GROUP .Load(sType,"UseSectionsWithPreferredGroup"); + HIDE_INACTIVE_SECTIONS .Load(sType,"OnlyShowActiveSection"); + REMIND_WHEEL_POSITIONS .Load(sType,"RemindWheelPositions"); + vector vsModeChoiceNames; + split( MODE_MENU_CHOICE_NAMES, ",", vsModeChoiceNames ); + CHOICE .Load(sType,CHOICE_NAME,vsModeChoiceNames); + SECTION_COLORS .Load(sType,SECTION_COLORS_NAME,NUM_SECTION_COLORS); + + CUSTOM_WHEEL_ITEM_NAMES .Load(sType,"CustomWheelItemNames"); + vector vsCustomItemNames; + split( CUSTOM_WHEEL_ITEM_NAMES, ",", vsCustomItemNames ); + CUSTOM_CHOICES.Load(sType,CUSTOM_WHEEL_ITEM_NAME,vsCustomItemNames); + CUSTOM_CHOICE_COLORS.Load(sType,CUSTOM_WHEEL_ITEM_COLOR,vsCustomItemNames); + + ROULETTE_COLOR .Load(sType,"RouletteColor"); + RANDOM_COLOR .Load(sType,"RandomColor"); + PORTAL_COLOR .Load(sType,"PortalColor"); + EMPTY_COLOR .Load(sType,"EmptyColor"); + + WheelBase::Load( sType ); + + SONGMAN->UpdateRankingCourses(); + + m_soundChangeSort.Load( THEME->GetPathS(sType,"sort") ); + m_soundExpand.Load( THEME->GetPathS(sType,"expand"), true ); + m_soundCollapse.Load( THEME->GetPathS(sType,"collapse"), true ); + + // Update for SORT_MOST_PLAYED. + SONGMAN->UpdatePopular(); + + /* Sort SONGMAN's songs by CompareSongPointersByTitle, so we can do other sorts (with + * stable_sort) from its output, and title will be the secondary sort, without having + * to re-sort by title each time. */ + SONGMAN->SortSongs(); + + + FOREACH_ENUM( SortOrder, so ) { + m_WheelItemDatasStatus[so]=INVALID; + } +} + +void MusicWheel::BeginScreen() +{ + RageTimer timer; + RString times; + FOREACH_ENUM( SortOrder, so ) { + if(m_WheelItemDatasStatus[so]!=INVALID) { + m_WheelItemDatasStatus[so]=NEEDREFILTER; + + } + + if(g_bPrecacheAllSorts) { + readyWheelItemsData(so); + times += ssprintf( "%i:%.3f ", so, timer.GetDeltaTime() ); + } + } + if(g_bPrecacheAllSorts) { + LOG->Trace( "MusicWheel sorting took: %s", times.c_str() ); + } + + // Set m_LastModeMenuItem to the first item that matches the current mode. (Do this + // after building wheel item data.) + { + const vector &from = getWheelItemsData(SORT_MODE_MENU); + for( unsigned i=0; im_pAction ); + if( from[i]->m_pAction->DescribesCurrentModeForAllPlayers() ) + { + m_sLastModeMenuItem = from[i]->m_pAction->m_sName; + break; + } + } + } + + WheelBase::BeginScreen(); + + if( GAMESTATE->IsAnExtraStageAndSelectionLocked() ) + { + m_WheelState = STATE_LOCKED; + SCREENMAN->PlayStartSound(); + m_fLockedWheelVelocity = 0; + } + + GAMESTATE->m_SortOrder.Set( GAMESTATE->m_PreferredSortOrder ); + + // Never start in the mode menu; some elements may not initialize correctly. + if( GAMESTATE->m_SortOrder == SORT_MODE_MENU ) + GAMESTATE->m_SortOrder.Set( SortOrder_Invalid ); + + GAMESTATE->m_SortOrder.Set( ForceAppropriateSort(GAMESTATE->m_PlayMode, GAMESTATE->m_SortOrder) ); + + /* Only save the sort order if the player didn't already have one. + * If he did, don't overwrite it. */ + if( GAMESTATE->m_PreferredSortOrder == SortOrder_Invalid ) + GAMESTATE->m_PreferredSortOrder = GAMESTATE->m_SortOrder; + + /* Invalidate current Song if it can't be played + * because there are not enough stages remaining. */ + if( GAMESTATE->m_pCurSong != NULL && + GameState::GetNumStagesMultiplierForSong( GAMESTATE->m_pCurSong ) > GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer() ) + { + GAMESTATE->m_pCurSong.Set( NULL ); + } + + /* Invalidate current Steps if it can't be played + * because there are not enough stages remaining. */ + FOREACH_ENUM( PlayerNumber, p ) + { + if( GAMESTATE->m_pCurSteps[p] != NULL ) + { + vector vpPossibleSteps; + if( GAMESTATE->m_pCurSong != NULL ) + SongUtil::GetPlayableSteps( GAMESTATE->m_pCurSong, vpPossibleSteps ); + bool bStepsIsPossible = find( vpPossibleSteps.begin(), vpPossibleSteps.end(), GAMESTATE->m_pCurSteps[p] ) == vpPossibleSteps.end(); + if( !bStepsIsPossible ) + GAMESTATE->m_pCurSteps[p].Set( NULL ); + } + } + + if(GAMESTATE->m_sPreferredSongGroup != GROUP_ALL) + { + // If a preferred song group is set, open the group and select the + // first song in the group. -aj + if(!GAMESTATE->IsCourseMode()) + { + vector vTemp = SONGMAN->GetSongs(GAMESTATE->m_sPreferredSongGroup); + ASSERT(vTemp.size() > 0); + GAMESTATE->m_pCurSong.Set(vTemp[0]); + }; + SetOpenSection(GAMESTATE->m_sPreferredSongGroup); + SelectSongOrCourse(); + } + else if( !SelectSongOrCourse() ) + { + // Select the the previously selected song (if any) + SetOpenSection(""); + } + + if( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) + { + // store the group song index, run this also here because it forgets the current position when + // not changing the song if you came back from gameplay or your last round song (profiles) + // is not the first one in the group. + for( unsigned idx = 0 ; idx < m_viWheelPositions.size() ; idx++ ) + { + if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(idx) ) + { + m_viWheelPositions[idx] = m_iSelection; + } + } + } + + // rebuild the WheelItems that appear on screen + RebuildWheelItems(); +} + +MusicWheel::~MusicWheel() +{ + FOREACH_ENUM( SortOrder, so ) { + vector::iterator i = m__UnFilteredWheelItemDatas[so].begin(); + vector::iterator iEnd = m__UnFilteredWheelItemDatas[so].end(); + for( ; i != iEnd; ++i ) { + delete *i; + } + + } +} + +/* If a song or course is set in GAMESTATE and available, select it. Otherwise, choose the + * first available song or course. Return true if an item was set, false if no items are + * available. */ +bool MusicWheel::SelectSongOrCourse() +{ + if( GAMESTATE->m_pPreferredSong && SelectSong( GAMESTATE->m_pPreferredSong ) ) + return true; + if( GAMESTATE->m_pCurSong && SelectSong( GAMESTATE->m_pCurSong ) ) + return true; + if( GAMESTATE->m_pPreferredCourse && SelectCourse( GAMESTATE->m_pPreferredCourse ) ) + return true; + if( GAMESTATE->m_pCurCourse && SelectCourse( GAMESTATE->m_pCurCourse ) ) + return true; + + // Select the first selectable song based on the sort order... + vector &wiWheelItems = getWheelItemsData(GAMESTATE->m_SortOrder); + for( unsigned i = 0; i < wiWheelItems.size(); i++ ) + { + if( wiWheelItems[i]->m_pSong ) + return SelectSong( wiWheelItems[i]->m_pSong ); + else if ( wiWheelItems[i]->m_pCourse ) + return SelectCourse( wiWheelItems[i]->m_pCourse ); + } + + LOG->Trace( "MusicWheel::MusicWheel() - No selectable songs or courses found in WheelData" ); + return false; +} + +bool MusicWheel::SelectSection( const RString & SectionName ) +{ + for( unsigned int i = 0; i < m_CurWheelItemData.size(); ++i ) + { + if( m_CurWheelItemData[i]->m_sText == SectionName ) + { + m_iSelection = i; // select it + return true; + } + } + + return false; +} + +bool MusicWheel::SelectSong( const Song *p ) +{ + if( p == NULL ) + return false; + + unsigned i; + vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); + for( i=0; im_pSong == p ) + { + // make its group the currently expanded group + SetOpenSection( from[i]->m_sText ); + break; + } + } + + if( i == from.size() ) + return false; + + for( i=0; im_pSong == p ) + m_iSelection = i; // select it + } + return true; +} + +bool MusicWheel::SelectCourse( const Course *p ) +{ + if( p == NULL ) + return false; + + unsigned i; + vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); + for( i=0; im_pCourse == p ) + { + // make its group the currently expanded group + SetOpenSection( from[i]->m_sText ); + break; + } + } + + if( i == from.size() ) + return false; + + for( i=0; im_pCourse == p ) + m_iSelection = i; // select it + } + + return true; +} + +bool MusicWheel::SelectModeMenuItem() +{ + // Select the last-chosen option. + ASSERT( GAMESTATE->m_SortOrder == SORT_MODE_MENU ); + const vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); + unsigned i; + for( i=0; im_pAction; + if( gc.m_sName == m_sLastModeMenuItem ) + break; + } + if( i == from.size() ) + return false; + + // make its group the currently expanded group + SetOpenSection( from[i]->m_sText ); + + for( i=0; im_pAction->m_sName != m_sLastModeMenuItem ) + continue; + m_iSelection = i; // select it + break; + } + + return true; +} + +// bool MusicWheel::SelectCustomItem() + +void MusicWheel::GetSongList( vector &arraySongs, SortOrder so ) +{ + vector apAllSongs; + switch( so ) + { + case SORT_PREFERRED: + SONGMAN->GetPreferredSortSongs( apAllSongs ); + break; + case SORT_POPULARITY: + apAllSongs = SONGMAN->GetPopularSongs(); + break; + case SORT_GROUP: + // if we're not using sections with a preferred song group, and there + // is a group to load, only load those songs. -aj + if(GAMESTATE->m_sPreferredSongGroup != GROUP_ALL && !USE_SECTIONS_WITH_PREFERRED_GROUP ) + { + apAllSongs = SONGMAN->GetSongs(GAMESTATE->m_sPreferredSongGroup); + break; + } + // otherwise fall through + default: + apAllSongs = SONGMAN->GetAllSongs(); + break; + } + + // filter songs that we don't have enough stages to play + { + vector vTempSongs; + SongCriteria sc; + sc.m_iMaxStagesForSong = GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer(); + SongUtil::FilterSongs( sc, apAllSongs, vTempSongs ); + apAllSongs = vTempSongs; + } + + // copy only songs that have at least one Steps for the current GameMode + for( unsigned i=0; iSongIsLocked( pSong ); + if( iLocked & LOCKED_DISABLED ) + continue; + + // If we're on an extra stage, and this song is selected, ignore #SELECTABLE. + if( pSong != GAMESTATE->m_pCurSong || !GAMESTATE->IsAnExtraStage() ) + { + // Hide songs that asked to be hidden via #SELECTABLE. + if( iLocked & LOCKED_SELECTABLE ) + continue; + if( so != SORT_ROULETTE && iLocked & LOCKED_ROULETTE ) + continue; + } + + /* Hide locked songs. If RANDOM_PICKS_LOCKED_SONGS, hide in Roulette + * and Random, too. */ + if( (so!=SORT_ROULETTE || !RANDOM_PICKS_LOCKED_SONGS) && iLocked ) + continue; + + if( PREFSMAN->m_bOnlyPreferredDifficulties ) + { + // if the song has steps that fit the preferred difficulty of the default player + if( pSong->HasStepsTypeAndDifficulty( GAMESTATE->GetCurrentStyle()->m_StepsType,GAMESTATE->m_PreferredDifficulty[GAMESTATE->GetFirstHumanPlayer()] ) ) + arraySongs.push_back( pSong ); + } + else + { + if(CommonMetrics::AUTO_SET_STYLE) + { + // with AUTO_SET_STYLE on and Autogen off, some songs may get + // hidden. Search through every playable StepsType until you + // find one, then add the song. + // see Issue 147 for more information. -aj + // http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=147 + set vStepsType; + SongUtil::GetPlayableStepsTypes( pSong, vStepsType ); + + FOREACHS( StepsType, vStepsType, st ) + { + if(pSong->HasStepsType(*st)) + { + arraySongs.push_back( pSong ); + break; + } + } + } + else + { + // If the song has at least one steps, add it. + if( pSong->HasStepsType(GAMESTATE->GetCurrentStyle()->m_StepsType) ) + arraySongs.push_back( pSong ); + } + } + } + + /* Hack: Add extra stage item if it was eliminated for any reason + * (eg. it's a long song). */ + if( GAMESTATE->IsAnExtraStage() ) + { + Song* pSong; + Steps* pSteps; + SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pSong, pSteps ); + + if( find( arraySongs.begin(), arraySongs.end(), pSong ) == arraySongs.end() ) + arraySongs.push_back( pSong ); + } +} + +void MusicWheel::BuildWheelItemDatas( vector &arrayWheelItemDatas, SortOrder so ) +{ + switch( so ) + { + case SORT_MODE_MENU: + { + arrayWheelItemDatas.clear(); // clear out the previous wheel items + vector vsNames; + split( MODE_MENU_CHOICE_NAMES, ",", vsNames ); + for( unsigned i=0; i( new GameCommand ); + wid.m_pAction->m_sName = vsNames[i]; + wid.m_pAction->Load( i, ParseCommands(CHOICE.GetValue(vsNames[i])) ); + wid.m_sLabel = WHEEL_TEXT( vsNames[i] ); + + if( !wid.m_pAction->IsPlayable() ) + continue; + + arrayWheelItemDatas.push_back( new MusicWheelItemData(wid) ); + } + break; + } + case SORT_PREFERRED: + case SORT_ROULETTE: + case SORT_GROUP: + case SORT_TITLE: + case SORT_BPM: + case SORT_POPULARITY: + case SORT_TOP_GRADES: + case SORT_ARTIST: + case SORT_GENRE: + case SORT_BEGINNER_METER: + case SORT_EASY_METER: + case SORT_MEDIUM_METER: + case SORT_HARD_METER: + case SORT_CHALLENGE_METER: + case SORT_LENGTH: + case SORT_RECENT: + { + // Make an array of Song*, then sort them + vector arraySongs; + GetSongList( arraySongs, so ); + + bool bUseSections = true; + + // sort the songs + switch( so ) + { + case SORT_PREFERRED: + // obey order specified by the preferred sort list + break; + case SORT_ROULETTE: + { + StepsType st; + Difficulty dc; + SongUtil::GetStepsTypeAndDifficultyFromSortOrder( SORT_EASY_METER, st, dc ); + SongUtil::SortSongPointerArrayByStepsTypeAndMeter( arraySongs, st, dc ); + if( (bool)PREFSMAN->m_bPreferredSortUsesGroups ) + stable_sort( arraySongs.begin(), arraySongs.end(), SongUtil::CompareSongPointersByGroup ); + bUseSections = false; + break; + } + case SORT_GROUP: + SongUtil::SortSongPointerArrayByGroupAndTitle( arraySongs ); + if(USE_SECTIONS_WITH_PREFERRED_GROUP) + bUseSections = true; + else + bUseSections = GAMESTATE->m_sPreferredSongGroup == GROUP_ALL; + break; + case SORT_TITLE: + SongUtil::SortSongPointerArrayByTitle( arraySongs ); + break; + case SORT_BPM: + SongUtil::SortSongPointerArrayByBPM( arraySongs ); + break; + case SORT_POPULARITY: + if( (int) arraySongs.size() > MOST_PLAYED_SONGS_TO_SHOW ) + arraySongs.erase( arraySongs.begin()+MOST_PLAYED_SONGS_TO_SHOW, arraySongs.end() ); + bUseSections = false; + break; + case SORT_TOP_GRADES: + SongUtil::SortSongPointerArrayByGrades( arraySongs, true ); + break; + case SORT_ARTIST: + SongUtil::SortSongPointerArrayByArtist( arraySongs ); + break; + case SORT_GENRE: + SongUtil::SortSongPointerArrayByGenre( arraySongs ); + break; + case SORT_LENGTH: + SongUtil::SortSongPointerArrayByLength( arraySongs ); + break; + case SORT_RECENT: + SongUtil::SortByMostRecentlyPlayedForMachine( arraySongs ); + if( (int) arraySongs.size() > RECENT_SONGS_TO_SHOW ) + arraySongs.erase( arraySongs.begin()+RECENT_SONGS_TO_SHOW, arraySongs.end() ); + bUseSections = false; + break; + case SORT_BEGINNER_METER: + case SORT_EASY_METER: + case SORT_MEDIUM_METER: + case SORT_HARD_METER: + case SORT_CHALLENGE_METER: + case SORT_DOUBLE_EASY_METER: + case SORT_DOUBLE_MEDIUM_METER: + case SORT_DOUBLE_HARD_METER: + case SORT_DOUBLE_CHALLENGE_METER: + StepsType st; + Difficulty dc; + SongUtil::GetStepsTypeAndDifficultyFromSortOrder( so, st, dc ); + SongUtil::SortSongPointerArrayByStepsTypeAndMeter( arraySongs, st, dc ); + break; + default: + FAIL_M("Unhandled sort order! Aborting..."); + } + + // Build an array of WheelItemDatas from the sorted list of Song*'s + arrayWheelItemDatas.clear(); // clear out the previous wheel items + arrayWheelItemDatas.reserve( arraySongs.size() ); + + switch( PREFSMAN->m_MusicWheelUsesSections ) + { + case MusicWheelUsesSections_NEVER: + bUseSections = false; + break; + case MusicWheelUsesSections_ABC_ONLY: + if( so != SORT_TITLE && so != SORT_GROUP ) + bUseSections = false; + break; + default: + break; + } + + if( bUseSections ) + { + // Sorting twice isn't necessary. Instead, modify the compatator + // functions in Song.cpp to have the desired effect. -Chris + /* Keeping groups together with the sorts is tricky and brittle; we + * keep getting OTHER split up without this. However, it puts the + * Grade and BPM sorts in the wrong order, and they're already correct, + * so don't re-sort for them. */ + /* We're using sections, so use the section name as the top-level sort. */ + switch( so ) + { + case SORT_PREFERRED: + case SORT_TOP_GRADES: + case SORT_BPM: + break; // don't sort by section + default: + SongUtil::SortSongPointerArrayBySectionName(arraySongs, so); + break; + } + } + + // make WheelItemDatas with sections + RString sLastSection = ""; + int iSectionColorIndex = 0; + for( unsigned i=0; i< arraySongs.size(); i++ ) + { + Song* pSong = arraySongs[i]; + if( bUseSections ) + { + RString sThisSection = SongUtil::GetSectionNameFromSongAndSort( pSong, so ); + + if( sThisSection != sLastSection ) + { + int iSectionCount = 0; + // Count songs in this section + unsigned j; + for( j=i; j < arraySongs.size(); j++ ) + { + if( SongUtil::GetSectionNameFromSongAndSort( arraySongs[j], so ) != sThisSection ) + break; + } + iSectionCount = j-i; + + // new section, make a section item + // todo: preferred sort section color handling? -aj + RageColor colorSection = (so==SORT_GROUP) ? SONGMAN->GetSongGroupColor(pSong->m_sGroupName) : SECTION_COLORS.GetValue(iSectionColorIndex); + iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS; + // In certain situations (e.g. simulating Pump it Up), themes may + // want to only show one group at a time. + if( !HIDE_INACTIVE_SECTIONS ) + arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, NULL, sThisSection, NULL, colorSection, iSectionCount) ); + sLastSection = sThisSection; + } + } + arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Song, pSong, sLastSection, NULL, SONGMAN->GetSongColor(pSong), 0) ); + } + + if( so != SORT_ROULETTE ) + { + // todo: allow themers to change the order of the items. -aj + if( SHOW_ROULETTE ) + arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Roulette, NULL, "", NULL, ROULETTE_COLOR, 0) ); + + // Only add WheelItemDataType_Random and WheelItemDataType_Portal if there's at least + // one song on the list. + bool bFoundAnySong = false; + for( unsigned i=0; !bFoundAnySong && i < arrayWheelItemDatas.size(); i++ ) + if( arrayWheelItemDatas[i]->m_Type == WheelItemDataType_Song ) + bFoundAnySong = true; + + if( SHOW_RANDOM && bFoundAnySong ) + arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Random, NULL, "", NULL, RANDOM_COLOR, 0) ); + + if( SHOW_PORTAL && bFoundAnySong ) + arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Portal, NULL, "", NULL, PORTAL_COLOR, 0) ); + + // add custom wheel items + vector vsNames; + split( CUSTOM_WHEEL_ITEM_NAMES, ",", vsNames ); + for( unsigned i=0; i( new GameCommand ); + wid.m_pAction->m_sName = vsNames[i]; + wid.m_pAction->Load( i, ParseCommands(CUSTOM_CHOICES.GetValue(vsNames[i])) ); + wid.m_sLabel = CUSTOM_ITEM_WHEEL_TEXT( vsNames[i] ); + + if( !wid.m_pAction->IsPlayable() ) + continue; + + arrayWheelItemDatas.push_back( new MusicWheelItemData(wid) ); + } + } + + if( GAMESTATE->IsAnExtraStageAndSelectionLocked() ) + { + Song* pSong; + Steps* pSteps; + SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pSong, pSteps ); + + for( unsigned i=0; im_pSong == pSong ) + { + // Change the song color. + arrayWheelItemDatas[i]->m_color = SONG_REAL_EXTRA_COLOR; + break; + } + } + } + break; + } + case SORT_ALL_COURSES: + case SORT_NONSTOP_COURSES: + case SORT_ONI_COURSES: + case SORT_ENDLESS_COURSES: + { + bool bOnlyPreferred = PREFSMAN->m_CourseSortOrder == COURSE_SORT_PREFERRED; + + vector vct; + switch( so ) + { + case SORT_NONSTOP_COURSES: + vct.push_back( COURSE_TYPE_NONSTOP ); + break; + case SORT_ONI_COURSES: + vct.push_back( COURSE_TYPE_ONI ); + vct.push_back( COURSE_TYPE_SURVIVAL ); + break; + case SORT_ENDLESS_COURSES: + vct.push_back( COURSE_TYPE_ENDLESS ); + break; + case SORT_ALL_COURSES: + FOREACH_ENUM( CourseType, i ) + vct.push_back( i ); + break; + default: ASSERT(0); break; + } + + vector apCourses; + FOREACH_CONST( CourseType, vct, ct ) + { + if( bOnlyPreferred ) + SONGMAN->GetPreferredSortCourses( *ct, apCourses, PREFSMAN->m_bAutogenGroupCourses ); + else + SONGMAN->GetCourses( *ct, apCourses, PREFSMAN->m_bAutogenGroupCourses ); + } + + switch( PREFSMAN->m_CourseSortOrder ) + { + case COURSE_SORT_SONGS: + CourseUtil::SortCoursePointerArrayByDifficulty( apCourses ); + break; + case COURSE_SORT_PREFERRED: + break; + case COURSE_SORT_METER: + CourseUtil::SortCoursePointerArrayByAvgDifficulty( apCourses ); + break; + case COURSE_SORT_METER_SUM: + CourseUtil::SortCoursePointerArrayByTotalDifficulty( apCourses ); + break; + case COURSE_SORT_RANK: + CourseUtil::SortCoursePointerArrayByRanking( apCourses ); + break; + default: FAIL_M("Impossible to sort the courses! Aborting..."); + } + + // since we can't agree, make it an option + if( PREFSMAN->m_CourseSortOrder != COURSE_SORT_SONGS && g_bMoveRandomToEnd ) + CourseUtil::MoveRandomToEnd( apCourses ); + + if( so == SORT_ALL_COURSES ) + CourseUtil::SortCoursePointerArrayByType( apCourses ); + + arrayWheelItemDatas.clear(); // clear out the previous wheel items + + RString sLastSection = ""; + int iSectionColorIndex = 0; + for( unsigned i=0; iCourseIsLocked(pCourse) ) + continue; + + RString sThisSection = ""; + if( so == SORT_ALL_COURSES ) + { + switch( pCourse->GetPlayMode() ) + { + case PLAY_MODE_ONI: sThisSection = "Oni"; break; + case PLAY_MODE_NONSTOP: sThisSection = "Nonstop"; break; + case PLAY_MODE_ENDLESS: sThisSection = "Endless"; break; + default: break; + } + } + + // check that this course has at least one song playable in the current style + if( !pCourse->IsPlayableIn(GAMESTATE->GetCurrentStyle()->m_StepsType) ) + continue; + + if( sThisSection != sLastSection ) // new section, make a section item + { + RageColor c = SECTION_COLORS.GetValue(iSectionColorIndex); + iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS; + arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, NULL, sThisSection, NULL, c, 0) ); + sLastSection = sThisSection; + } + + RageColor c = ( pCourse->m_sGroupName.size() == 0 ) ? pCourse->GetColor() : SONGMAN->GetCourseColor(pCourse); + arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Course, NULL, sThisSection, pCourse, c, 0) ); + } + break; + } + default: + break; + } + + // init music status icons + for( unsigned i=0; iIsEasy( GAMESTATE->GetCurrentStyle()->m_StepsType ) && SHOW_EASY_FLAG; + WID.m_Flags.bEdits = false; + set vStepsType; + SongUtil::GetPlayableStepsTypes( WID.m_pSong, vStepsType ); + FOREACHS( StepsType, vStepsType, st ) + WID.m_Flags.bEdits |= WID.m_pSong->HasEdits( *st ); + WID.m_Flags.iStagesForSong = GameState::GetNumStagesMultiplierForSong( WID.m_pSong ); + } + else if( WID.m_pCourse != NULL ) + { + WID.m_Flags.bHasBeginnerOr1Meter = false; + WID.m_Flags.bEdits = WID.m_pCourse->IsAnEdit(); + WID.m_Flags.iStagesForSong = 1; + } + } +} + +vector & MusicWheel::getWheelItemsData(SortOrder so) { + // Update the popularity and init icons. + readyWheelItemsData(so); + return m__WheelItemDatas[so]; +} + +void MusicWheel::readyWheelItemsData(SortOrder so) { + if(m_WheelItemDatasStatus[so]!=VALID) { + RageTimer timer; + + vector &aUnFilteredDatas=m__UnFilteredWheelItemDatas[so]; + + if(m_WheelItemDatasStatus[so]==INVALID) { + BuildWheelItemDatas( aUnFilteredDatas, so ); + } + FilterWheelItemDatas( aUnFilteredDatas, m__WheelItemDatas[so], so ); + m_WheelItemDatasStatus[so]=VALID; + + LOG->Trace( "MusicWheel sorting took: %f", timer.GetTimeSinceStart() ); + } + +} + +void MusicWheel::FilterWheelItemDatas(vector &aUnFilteredDatas, vector &aFilteredData, SortOrder so ) +{ + + unsigned unfilteredSize=aUnFilteredDatas.size(); + + /* Only add WheelItemDataType_Portal if there's at least one song on the list. */ + bool bFoundAnySong = false; + for( unsigned i=0; i < unfilteredSize; i++ ) { + if( aUnFilteredDatas[i]->m_Type == WheelItemDataType_Song ) { + bFoundAnySong = true; + break; + } + } + + vector aiRemove; + aiRemove.insert( aiRemove.begin(), unfilteredSize, false ); + + const int iMaxStagesForSong = GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer(); + + Song *pExtraStageSong = NULL; + if( GAMESTATE->IsAnExtraStage() ) + { + Steps *pSteps; + SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pExtraStageSong, pSteps ); + } + + /* Mark any songs that aren't playable in aiRemove. */ + + for( unsigned i=0; i< unfilteredSize; i++ ) + { + MusicWheelItemData& WID = *aUnFilteredDatas[i]; + + /* If we have no songs, remove Random and Portal. */ + if( WID.m_Type == WheelItemDataType_Random || WID.m_Type == WheelItemDataType_Portal ) + { + if( !bFoundAnySong ) + aiRemove[i] = true; + continue; + } + + /* Filter songs that we don't have enough stages to play. */ + if( WID.m_Type == WheelItemDataType_Song ) + { + Song* pSong = WID.m_pSong; + + /* Never remove the extra stage song. */ + if( pExtraStageSong && WID.m_pSong == pExtraStageSong ) + continue; + + /* Check that we have enough stages to play this song. */ + if( GAMESTATE->GetNumStagesMultiplierForSong(WID.m_pSong) > iMaxStagesForSong ) + { + aiRemove[i] = true; + continue; + } + + int iLocked = UNLOCKMAN->SongIsLocked( pSong ); + if( iLocked & LOCKED_DISABLED ) + { + aiRemove[i] = true; + continue; + } + + /* If we're on an extra stage, and this song is selected, ignore #SELECTABLE. */ + if( pSong != GAMESTATE->m_pCurSong || !GAMESTATE->IsAnExtraStage() ) + { + /* Hide songs that asked to be hidden via #SELECTABLE. */ + if( iLocked & LOCKED_SELECTABLE ) + { + aiRemove[i] = true; + continue; + } + if( so != SORT_ROULETTE && iLocked & LOCKED_ROULETTE ) + { + aiRemove[i] = true; + continue; + } + } + + /* Hide locked songs. If RANDOM_PICKS_LOCKED_SONGS, hide in Roulette and Random, + * too. */ + if( (so!=SORT_ROULETTE || !RANDOM_PICKS_LOCKED_SONGS) && iLocked ) + { + aiRemove[i] = true; + continue; + } + + /* If the song has no steps for the current style, remove it. */ + if( !CommonMetrics::AUTO_SET_STYLE && !pSong->HasStepsType(GAMESTATE->GetCurrentStyle()->m_StepsType) ) + { + aiRemove[i] = true; + continue; + } + + // if AutoSetStyle, make sure the song is playable in the end. + if (!SongUtil::IsSongPlayable(pSong)) + { + aiRemove[i] = true; + continue; + } + } + + if( WID.m_Type == WheelItemDataType_Course ) + { + if( !WID.m_pCourse->IsPlayableIn(GAMESTATE->GetCurrentStyle()->m_StepsType) ) + aiRemove[i] = true; + } + } + + /* Filter out the songs we're removing. */ + + aFilteredData.reserve( unfilteredSize ); + for( unsigned i=0; i< unfilteredSize; i++ ) + { + if( aiRemove[i] ) + continue; + aFilteredData.push_back( aUnFilteredDatas[i] ); + } + + // Update the song count in each section header. + unsigned filteredSize=aFilteredData.size(); + for( unsigned i=0; i < filteredSize; ) + { + MusicWheelItemData& WID = *aFilteredData[i]; + ++i; + if( WID.m_Type != WheelItemDataType_Section ) + continue; + + // Count songs in this section + WID.m_iSectionCount = 0; + for( ; i < filteredSize && aFilteredData[i]->m_sText == WID.m_sText; ++i ) + ++WID.m_iSectionCount; + } + + // If we have any section headers with no songs, then we filtered all of the songs in that group, + // so remove it. This isn't optimized like the above since this is a rare case. + for( unsigned i=0; i < filteredSize; ++i ) + { + MusicWheelItemData& WID = *aFilteredData[i]; + if( WID.m_Type != WheelItemDataType_Section ) + continue; + if( WID.m_iSectionCount > 0 ) + continue; + aFilteredData.erase( aFilteredData.begin()+i, aFilteredData.begin()+i+1 ); + --i; + --filteredSize; + } + + /* Update the popularity. This is affected by filtering. */ + if( so == SORT_POPULARITY ) + { + for( unsigned i=0; i< min(3u,aFilteredData.size()); i++ ) + { + MusicWheelItemData& WID = *aFilteredData[i]; + WID.m_Flags.iPlayersBestNumber = i+1; + } + } + + // If we've filtered all items, insert a dummy. + if( aFilteredData.empty() ) + aFilteredData.push_back( new MusicWheelItemData(WheelItemDataType_Section, NULL, EMPTY_STRING, NULL, EMPTY_COLOR, 0) ); +} + +void MusicWheel::UpdateSwitch() +{ + switch( m_WheelState ) + { + case STATE_FLYING_OFF_BEFORE_NEXT_SORT: + { + const Song* pPrevSelectedSong = GetCurWheelItemData(m_iSelection)->m_pSong; + + SCREENMAN->PostMessageToTopScreen( SM_SortOrderChanged, 0 ); + + SetOpenSection( SongUtil::GetSectionNameFromSongAndSort(pPrevSelectedSong, GAMESTATE->m_SortOrder) ); + + m_iSelection = 0; + + // Select the previously selected item + switch( GAMESTATE->m_SortOrder ) + { + default: + // Look for the last selected song or course + SelectSongOrCourse(); + break; + case SORT_MODE_MENU: + SelectModeMenuItem(); + break; + } + + // Change difficulty for sorts by meter + // XXX: do this with GameCommand? + StepsType st; + Difficulty dc; + if( SongUtil::GetStepsTypeAndDifficultyFromSortOrder( GAMESTATE->m_SortOrder, st, dc ) ) + { + ASSERT( dc != Difficulty_Invalid ); + FOREACH_PlayerNumber( p ) + if( GAMESTATE->IsPlayerEnabled(p) ) + GAMESTATE->m_PreferredDifficulty[p].Set( dc ); + } + + SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); + RebuildWheelItems(); + TweenOnScreenForSort(); + } + break; + + case STATE_FLYING_ON_AFTER_NEXT_SORT: + m_WheelState = STATE_SELECTING; // now, wait for input + break; + + case STATE_SELECTING: + m_fTimeLeftInState = 0; + break; + case STATE_ROULETTE_SPINNING: + case STATE_RANDOM_SPINNING: + break; + case STATE_LOCKED: + break; + case STATE_ROULETTE_SLOWING_DOWN: + if( m_iSwitchesLeftInSpinDown == 0 ) + { + m_WheelState = STATE_LOCKED; + m_fTimeLeftInState = 0; + SCREENMAN->PlayStartSound(); + m_fLockedWheelVelocity = 0; + + // Send this again so the screen starts sample music. + SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); + MESSAGEMAN->Broadcast("RouletteStopped"); + } + else + { + --m_iSwitchesLeftInSpinDown; + const float SwitchTimes[] = { 0.5f, 1.3f, 0.8f, 0.4f, 0.2f }; + ASSERT( m_iSwitchesLeftInSpinDown >= 0 && m_iSwitchesLeftInSpinDown <= 4 ); + m_fTimeLeftInState = SwitchTimes[m_iSwitchesLeftInSpinDown]; + m_Moving = 0; + + LOG->Trace( "m_iSwitchesLeftInSpinDown id %d, m_fTimeLeftInState is %f", m_iSwitchesLeftInSpinDown, m_fTimeLeftInState ); + + if( m_iSwitchesLeftInSpinDown == 0 ) + ChangeMusic( randomf(0,1) >= 0.5f? 1:-1 ); + else + ChangeMusic( 1 ); + } + break; + default: + ASSERT(0); // all state changes should be handled explicitly + break; + } +} + +void MusicWheel::ChangeMusic( int iDist ) +{ + m_iSelection += iDist; + wrap( m_iSelection, m_CurWheelItemData.size() ); + + if( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) + { + // store the group song index + for( unsigned idx = 0 ; idx < m_viWheelPositions.size() ; idx++ ) + { + if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(idx) ) + { + m_viWheelPositions[idx] = m_iSelection; + } + } + } + + RebuildWheelItems( iDist ); + + m_fPositionOffsetFromSelection += iDist; + + SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); + + // If we're moving automatically, don't play this; it'll be called in Update. + if(!IsMoving()) + m_soundChangeMusic.Play(); +} + + +bool MusicWheel::ChangeSort( SortOrder new_so, bool allowSameSort ) // return true if change successful +{ + ASSERT( new_so < NUM_SortOrder ); + if( GAMESTATE->m_SortOrder == new_so && !allowSameSort ) + return false; + + // Don't change to SORT_MODE_MENU if it doesn't have at least two choices. + if( new_so == SORT_MODE_MENU && getWheelItemsData(new_so).size() < 2 ) + return false; + + switch( m_WheelState ) + { + case STATE_SELECTING: + case STATE_FLYING_ON_AFTER_NEXT_SORT: + break; // fall through + default: + return false; // don't continue + } + + SCREENMAN->PostMessageToTopScreen( SM_SortOrderChanging, 0 ); + + m_soundChangeSort.Play(); + + TweenOffScreenForSort(); + + // Save the new preference. + if( IsSongSort(new_so) ) + GAMESTATE->m_PreferredSortOrder = new_so; + GAMESTATE->m_SortOrder.Set( new_so ); + + return true; +} + +bool MusicWheel::NextSort() // return true if change successful +{ + // don't allow NextSort when on the mode menu + if( GAMESTATE->m_SortOrder == SORT_MODE_MENU ) + return false; + + vector aSortOrders; + { + Lua *L = LUA->Get(); + SORT_ORDERS.PushSelf( L ); + FOREACH_LUATABLEI( L, -1, i ) + { + SortOrder so = Enum::Check( L, -1, true ); + aSortOrders.push_back( so ); + } + lua_pop( L, 1 ); + LUA->Release(L); + } + + // find the index of the current sort + int cur = 0; + while( cur < int(aSortOrders.size()) && aSortOrders[cur] != GAMESTATE->m_SortOrder ) + ++cur; + + // move to the next sort with wrapping + ++cur; + wrap( cur, aSortOrders.size() ); + + // apply new sort + SortOrder soNew = aSortOrders[cur]; + return ChangeSort( soNew ); +} + +bool MusicWheel::Select() // return true if this selection ends the screen +{ + LOG->Trace( "MusicWheel::Select()" ); + + switch( m_WheelState ) + { + case STATE_FLYING_OFF_BEFORE_NEXT_SORT: + case STATE_ROULETTE_SLOWING_DOWN: + return false; + case STATE_ROULETTE_SPINNING: + m_WheelState = STATE_ROULETTE_SLOWING_DOWN; + m_iSwitchesLeftInSpinDown = ROULETTE_SLOW_DOWN_SWITCHES/2+1 + RandomInt( ROULETTE_SLOW_DOWN_SWITCHES/2 ); + m_fTimeLeftInState = 0.1f; + return false; + case STATE_RANDOM_SPINNING: + m_fPositionOffsetFromSelection = max(m_fPositionOffsetFromSelection, 0.3f); + m_WheelState = STATE_LOCKED; + SCREENMAN->PlayStartSound(); + m_fLockedWheelVelocity = 0; + // Set m_Moving to zero to stop the sounds from playing. + m_Moving = 0; + SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); + return true; + default: break; + } + + if( !WheelBase::Select() ) + return false; + + switch( m_CurWheelItemData[m_iSelection]->m_Type ) + { + case WheelItemDataType_Roulette: + StartRoulette(); + return false; + case WheelItemDataType_Random: + StartRandom(); + return false; + case WheelItemDataType_Sort: + GetCurWheelItemData(m_iSelection)->m_pAction->ApplyToAllPlayers(); + ChangeSort( GAMESTATE->m_PreferredSortOrder ); + m_sLastModeMenuItem = GetCurWheelItemData(m_iSelection)->m_pAction->m_sName; + return false; + case WheelItemDataType_Custom: + GetCurWheelItemData(m_iSelection)->m_pAction->ApplyToAllPlayers(); + if( GetCurWheelItemData(m_iSelection)->m_pAction->m_sScreen != "" ) + return true; + else + return false; + default: return true; + } +} + +void MusicWheel::StartRoulette() +{ + MESSAGEMAN->Broadcast("StartRoulette"); + m_WheelState = STATE_ROULETTE_SPINNING; + m_Moving = 1; + m_TimeBeforeMovingBegins = 0; + m_SpinSpeed = 1.0f/ROULETTE_SWITCH_SECONDS; + GAMESTATE->m_SortOrder.Set( SORT_ROULETTE ); + SetOpenSection( "" ); + RebuildWheelItems(); +} + +void MusicWheel::StartRandom() +{ + MESSAGEMAN->Broadcast("StartRandom"); + /* If RANDOM_PICKS_LOCKED_SONGS is disabled, pick a song from the active sort and + * section. If enabled, picking from the section makes it too easy to trick the + * game into picking a locked song, so pick from SORT_ROULETTE. */ + if( RANDOM_PICKS_LOCKED_SONGS ) + { + // Shuffle and use the roulette wheel. + RandomGen rnd; + random_shuffle( getWheelItemsData(SORT_ROULETTE).begin(), getWheelItemsData(SORT_ROULETTE).end(), rnd ); + GAMESTATE->m_SortOrder.Set( SORT_ROULETTE ); + } + else + { + GAMESTATE->m_SortOrder.Set( GAMESTATE->m_PreferredSortOrder ); + } + SetOpenSection( "" ); + + m_Moving = -1; + m_TimeBeforeMovingBegins = 0; + m_SpinSpeed = 1.0f/ROULETTE_SWITCH_SECONDS; + m_SpinSpeed *= 20.0f; /* faster! */ + m_WheelState = STATE_RANDOM_SPINNING; + + SelectSong( GetPreferredSelectionForRandomOrPortal() ); + + RebuildWheelItems(); +} + +void MusicWheel::SetOpenSection( RString group ) +{ + //LOG->Trace( "SetOpenSection %s", group.c_str() ); + m_sExpandedSectionName = group; + GAMESTATE->sExpandedSectionName = group; + + // wheel positions = num song groups + if ( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) + m_viWheelPositions.resize( SONGMAN->GetNumSongGroups() ); + + const WheelItemBaseData *old = NULL; + if( !m_CurWheelItemData.empty() ) + old = GetCurWheelItemData(m_iSelection); + + vector vpPossibleStyles; + if( CommonMetrics::AUTO_SET_STYLE ) + GAMEMAN->GetCompatibleStyles( GAMESTATE->m_pCurGame, GAMESTATE->GetNumPlayersEnabled(), vpPossibleStyles ); + + m_CurWheelItemData.clear(); + vector &from = getWheelItemsData(GAMESTATE->m_SortOrder); + m_CurWheelItemData.reserve( from.size() ); + for( unsigned i = 0; i < from.size(); ++i ) + { + MusicWheelItemData &d = *from[i]; + if( (d.m_Type == WheelItemDataType_Song || d.m_Type == WheelItemDataType_Course) && !d.m_sText.empty() && + d.m_sText != group ) + continue; + + // If AUTO_SET_STYLE, hide courses that prefer a style that isn't available. + if( d.m_Type == WheelItemDataType_Course && CommonMetrics::AUTO_SET_STYLE ) + { + const Style *pStyle = d.m_pCourse->GetCourseStyle( GAMESTATE->m_pCurGame, GAMESTATE->GetNumSidesJoined() ); + if( pStyle ) + { + if( find( vpPossibleStyles.begin(), vpPossibleStyles.end(), pStyle ) == vpPossibleStyles.end() ) + continue; + } + } + + // Only show tutorial songs in arcade + if( GAMESTATE->m_PlayMode!=PLAY_MODE_REGULAR && + d.m_pSong && + d.m_pSong->IsTutorial() ) + continue; + + m_CurWheelItemData.push_back(&d); + } + + //restore the past group song index + if( REMIND_WHEEL_POSITIONS && HIDE_INACTIVE_SECTIONS ) + { + for( unsigned idx = 0 ; idx < m_viWheelPositions.size() ; idx++ ) + { + if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(idx) ) + { + m_iSelection = m_viWheelPositions[idx]; + } + } + } + else + { + // Try to select the item that was selected before changing groups + m_iSelection = 0; + + for( unsigned i=0; iGetNumSongGroups(); + + for(unsigned i = 0 ; i < iNumGroups ; i++) + { + if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(i) ) + { + if ( i < iNumGroups - 1 ) + return SONGMAN->GetSongGroupByIndex(i+1); + else + { + //i = 0; + return SONGMAN->GetSongGroupByIndex(0); + } + } + } + } + else + { + unsigned int iLastSelection = m_iSelection; + for( unsigned int i = m_iSelection; i < m_CurWheelItemData.size(); ++i ) + { + if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section && i != (unsigned int)m_iSelection ) + { + m_iSelection = i; + return m_CurWheelItemData[i]->m_sText; + } + } + // it should not get down here, but it might happen... only search up to + // the previous selection. + for( unsigned int i = 0; i < iLastSelection; ++i ) + { + if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section && i != (unsigned int)m_iSelection ) + { + m_iSelection = i; + return m_CurWheelItemData[i]->m_sText; + } + } + } + // it shouldn't get here, but just in case... + return ""; +} + +RString MusicWheel::JumpToPrevGroup() +{ + if( HIDE_INACTIVE_SECTIONS ) + { + unsigned iNumGroups = SONGMAN->GetNumSongGroups(); + + for(unsigned i = 0 ; i < iNumGroups ; i++) + { + if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(i) ) + { + if ( i > 0 ) + return SONGMAN->GetSongGroupByIndex(i-1); + else + { + //i = iNumGroups - 1; + return SONGMAN->GetSongGroupByIndex(iNumGroups - 1); + } + } + } + } + else + { + for( unsigned int i = m_iSelection; i > 0; --i ) + { + if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section && i != (unsigned int)m_iSelection ) + { + m_iSelection = i; + return m_CurWheelItemData[i]->m_sText; + } + } + // in case it wasn't found above: + for( unsigned int i = m_CurWheelItemData.size()-1; i > 0; --i ) + { + LOG->Trace( "JumpToPrevGroup iteration 2 | i = %u",i ); + if( m_CurWheelItemData[i]->m_Type == WheelItemDataType_Section ) + { + m_iSelection = i; + LOG->Trace( "finding it in #2 | i = %u | text = %s",i, m_CurWheelItemData[i]->m_sText.c_str() ); + return m_CurWheelItemData[i]->m_sText; + } + } + } + // it shouldn't get here, but just in case... + return ""; +} + +// Called on late join. Selectable courses may have changed; reopen the section. +void MusicWheel::PlayerJoined() +{ + // TRICKY: If Autogen is off and someone joins, the first player may be on + // a song that has an illegal stepstype for the current amount of players. + // (e.g. a song that only has doubles difficulties and a second player joins.) + // We need to rebuild the wheel item data in this situation. -aj + if( !GAMESTATE->IsCourseMode() && !PREFSMAN->m_bAutogenSteps ) + { + FOREACH_ENUM(SortOrder, so) + { + m_WheelItemDatasStatus[so] = INVALID; + } + RebuildWheelItems(); + } + SetOpenSection( m_sExpandedSectionName ); +} + +bool MusicWheel::IsRouletting() const +{ + return m_WheelState == STATE_ROULETTE_SPINNING || m_WheelState == STATE_ROULETTE_SLOWING_DOWN || + m_WheelState == STATE_RANDOM_SPINNING; +} + +Song* MusicWheel::GetSelectedSong() +{ + switch( m_CurWheelItemData[m_iSelection]->m_Type ) + { + case WheelItemDataType_Portal: + return GetPreferredSelectionForRandomOrPortal(); + default: + return GetCurWheelItemData(m_iSelection)->m_pSong; + } +} + +/* Find a random song. If possible, find one that has the preferred difficulties of + * each player. Prefer songs in the active group, if any. + * + * Note that if this is called, we *must* find a song. We will only be called if + * the active sort has at least one song, but there may be no open group. This means + * that any filters and preferences applied here must be optional. */ +Song *MusicWheel::GetPreferredSelectionForRandomOrPortal() +{ + // probe to find a song that has the preferred + // difficulties of each player + vector vDifficultiesToRequire; + FOREACH_HumanPlayer(p) + { + if( GAMESTATE->m_PreferredDifficulty[p] == Difficulty_Invalid ) + continue; // skip + + // TRICKY: Don't require that edits be present if perferred + // difficulty is Difficulty_Edit. Otherwise, players could use this + // to set up a 100% chance of getting a particular locked song by + // having a single edit for a locked song. + if( GAMESTATE->m_PreferredDifficulty[p] == Difficulty_Edit ) + continue; // skip + + vDifficultiesToRequire.push_back( GAMESTATE->m_PreferredDifficulty[p] ); + } + + RString sPreferredGroup = m_sExpandedSectionName; + vector &wid = getWheelItemsData(GAMESTATE->m_SortOrder); + + StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType; + +#define NUM_PROBES 1000 + for( int i=0; im_Type != WheelItemDataType_Song ) + continue; + + const Song *pSong = wid[iSelection]->m_pSong; + + if( !sPreferredGroup.empty() && wid[iSelection]->m_sText != sPreferredGroup ) + continue; + + // There's an off possibility that somebody might have only one song with only beginner steps. + if( i < 900 && pSong->IsTutorial() ) + continue; + + FOREACH( Difficulty, vDifficultiesToRequire, d ) + if( !pSong->HasStepsTypeAndDifficulty(st,*d) ) + goto try_next; + return wid[iSelection]->m_pSong; +try_next: + ; + } + LOG->Warn( "Couldn't find any songs" ); + return wid[0]->m_pSong; +} + +void MusicWheel::FinishChangingSorts() +{ + FinishTweening(); + m_WheelState = STATE_SELECTING; + m_fTimeLeftInState = 0; +} + +// lua start +#include "LuaBinding.h" + +class LunaMusicWheel: public Luna +{ +public: + static int IsRouletting( T* p, lua_State *L ){ lua_pushboolean( L, p->IsRouletting() ); return 1; } + + LunaMusicWheel() + { + ADD_METHOD( IsRouletting ); + } +}; + +/* + * (c) 2001-2004 Chris Danford, Chris Gomez, Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */