2 #include "MusicWheel.h"
3 #include "SongManager.h"
4 #include "ScreenManager.h" // for sending SM_PlayMusicSample
9 #include "UnlockManager.h"
10 #include "ActorUtil.h"
12 #include "CourseUtil.h"
14 #include "PlayerState.h"
16 #define NUM_WHEEL_ITEMS ((int)ceil(NUM_WHEEL_ITEMS_TO_DRAW+2))
18 static CString
SECTION_COLORS_NAME( size_t i
) { return ssprintf("SectionColor%d",int(i
+1)); }
19 static CString
CHOICE_NAME( CString s
) { return ssprintf("Choice%s",s
.c_str()); }
21 AutoScreenMessage( SM_SongChanged
) // TODO: Replace this with a Message and MESSAGEMAN
22 AutoScreenMessage( SM_SortOrderChanging
);
23 AutoScreenMessage( SM_SortOrderChanged
);
25 const int MAX_WHEEL_SOUND_SPEED
= 15;
27 static const SortOrder g_SongSortOrders
[] =
37 const vector
<SortOrder
> SONG_SORT_ORDERS( g_SongSortOrders
, g_SongSortOrders
+ ARRAYLEN(g_SongSortOrders
) );
39 MusicWheel
::MusicWheel()
43 SortOrder
ForceAppropriateSort( PlayMode pm
, SortOrder so
)
47 // in course modes, force a particular sort
48 case PLAY_MODE_ONI
: return SORT_ONI_COURSES
;
49 case PLAY_MODE_NONSTOP
: return SORT_NONSTOP_COURSES
;
50 case PLAY_MODE_ENDLESS
: return SORT_ENDLESS_COURSES
;
53 /* If we're not in a course mode, don't start in a course sort. */
56 case SORT_ONI_COURSES
:
57 case SORT_NONSTOP_COURSES
:
58 case SORT_ENDLESS_COURSES
:
67 void MusicWheel
::Load( CString sType
)
69 LOG
->Trace( "MusicWheel::Load('%s')", sType
.c_str() );
71 LoadFromMetrics( sType
);
74 FOREACH( MusicWheelItem
*, m_MusicWheelItems
, i
)
76 m_MusicWheelItems
.clear();
77 for( int i
=0; i
<NUM_WHEEL_ITEMS
; i
++ )
78 m_MusicWheelItems
.push_back( new MusicWheelItem
);
81 LOG
->Trace( "MusicWheel::Load('%s')", sType
.c_str() );
82 if (GAMESTATE
->m_pCurSong
!= NULL
)
83 LOG
->Trace( "Current Song: %s", GAMESTATE
->m_pCurSong
->GetSongDir().c_str() );
85 LOG
->Trace( "Current Song: NULL" );
87 SONGMAN
->UpdateRankingCourses();
91 // Whatever Screen uses MusicWheel should set the Style if it needs to be set.
92 if( GAMESTATE->m_CurStyle == NULL )
93 GAMESTATE->m_CurStyle = GAMEMAN->STYLE_DANCE_SINGLE;
96 /* We play a lot of this one, so precache it. */
97 m_soundChangeSort
.Load( THEME
->GetPathS(sType
,"sort") );
98 m_soundExpand
.Load( THEME
->GetPathS(sType
,"expand"), true );
100 m_WheelState
= STATE_SELECTING_MUSIC
;
102 if( GAMESTATE
->IsExtraStage() || GAMESTATE
->IsExtraStage2() )
104 // make the preferred group the group of the last song played.
105 // if( GAMESTATE->m_sPreferredSongGroup==GROUP_ALL_MUSIC && !PREFSMAN->m_bPickExtraStage )
107 // ASSERT(GAMESTATE->m_pCurSong);
108 // GAMESTATE->m_sPreferredSongGroup = GAMESTATE->m_pCurSong->m_sGroupName;
115 SONGMAN
->GetExtraStageInfo(
116 GAMESTATE
->IsExtraStage2(),
117 GAMESTATE
->GetCurrentStyle(),
122 GAMESTATE
->m_pCurSong
.Set( pSong
);
123 GAMESTATE
->m_pPreferredSong
= pSong
;
124 FOREACH_HumanPlayer( p
)
126 GAMESTATE
->m_pCurSteps
[p
].Set( pSteps
);
127 GAMESTATE
->m_pPlayerState
[p
]->m_PlayerOptions
= po
;
128 GAMESTATE
->m_PreferredDifficulty
[p
].Set( pSteps
->GetDifficulty() );
130 GAMESTATE
->m_SongOptions
= so
;
133 GAMESTATE
->m_SortOrder
= GAMESTATE
->m_PreferredSortOrder
;
135 /* Never start in the mode menu; some elements may not initialize correctly. */
136 if( GAMESTATE
->m_SortOrder
== SORT_MODE_MENU
)
137 GAMESTATE
->m_SortOrder
= SORT_INVALID
;
139 GAMESTATE
->m_SortOrder
= ForceAppropriateSort( GAMESTATE
->m_PlayMode
, GAMESTATE
->m_SortOrder
);
141 /* Only save the sort order if the player didn't already have one. If he did, don't
143 if( GAMESTATE
->m_PreferredSortOrder
== SORT_INVALID
)
144 GAMESTATE
->m_PreferredSortOrder
= GAMESTATE
->m_SortOrder
;
146 /* Update for SORT_MOST_PLAYED. */
147 SONGMAN
->UpdateBest();
149 /* Sort SONGMAN's songs by CompareSongPointersByTitle, so we can do other sorts (with
150 * stable_sort) from its output, and title will be the secondary sort, without having
151 * to re-sort by title each time. */
152 SONGMAN
->SortSongs();
156 /* Build all of the wheel item data. Do this after selecting
157 * the extra stage, so it knows to always display it. */
158 FOREACH_SortOrder( so
)
160 BuildWheelItemDatas( m_WheelItemDatas
[so
], so
);
161 times
+= ssprintf( "%i:%.3f ", so
, timer
.GetDeltaTime() );
163 LOG
->Trace( "took: %s", times
.c_str() );
165 /* Set m_LastModeMenuItem to the first item that matches the current mode. (Do this
166 * after building wheel item data.) */
168 const vector
<WheelItemData
> &from
= m_WheelItemDatas
[SORT_MODE_MENU
];
169 for( unsigned i
=0; i
<from
.size(); i
++ )
170 if( from
[i
].m_Action
.DescribesCurrentModeForAllPlayers() )
172 m_sLastModeMenuItem
= from
[i
].m_Action
.m_sName
;
178 // HACK: invalidate currently selected song in the case that it
179 // cannot be played due to lack of stages remaining
180 // checking for event mode shouldn't be necessary here
181 // but someone mentioned it does it sometimes.
182 if( GAMESTATE
->m_pCurSong
!= NULL
&&
183 SongManager
::GetNumStagesForSong( GAMESTATE
->m_pCurSong
) + GAMESTATE
->m_iCurrentStageIndex
> PREFSMAN
->m_iSongsPerPlay
184 && !GAMESTATE
->IsEventMode()
185 && !GAMESTATE
->IsExtraStage() && !GAMESTATE
->IsExtraStage2() )
187 GAMESTATE
->m_pCurSong
.Set( NULL
);
190 // Select the the previously selected song (if any)
191 if( !SelectSongOrCourse() )
194 // rebuild the WheelItems that appear on screen
195 RebuildAllMusicWheelItems();
198 void MusicWheel
::LoadFromMetrics( CString sType
)
200 WheelBase
::LoadFromMetrics(sType
);
202 ROULETTE_SWITCH_SECONDS
.Load(sType
,"RouletteSwitchSeconds");
203 ROULETTE_SLOW_DOWN_SWITCHES
.Load(sType
,"RouletteSlowDownSwitches");
204 NUM_SECTION_COLORS
.Load(sType
,"NumSectionColors");
205 SONG_REAL_EXTRA_COLOR
.Load(sType
,"SongRealExtraColor");
206 SORT_MENU_COLOR
.Load(sType
,"SortMenuColor");
207 SHOW_ROULETTE
.Load(sType
,"ShowRoulette");
208 SHOW_RANDOM
.Load(sType
,"ShowRandom");
209 SHOW_PORTAL
.Load(sType
,"ShowPortal");
210 RANDOM_PICKS_LOCKED_SONGS
.Load(sType
,"RandomPicksLockedSongs");
211 MOST_PLAYED_SONGS_TO_SHOW
.Load(sType
,"MostPlayedSongsToShow");
212 MODE_MENU_CHOICE_NAMES
.Load(sType
,"ModeMenuChoiceNames");
213 SWAP_RANDOM_AND_ROULETTE
.Load(sType
,"SwapRandomAndRoulette"); //Self-Explaining. Swaps Random and Roulette in the wheel. -Wanny&Kriz
214 vector
<CString
> vsModeChoiceNames
;
215 split( MODE_MENU_CHOICE_NAMES
, ",", vsModeChoiceNames
);
216 CHOICE
.Load(sType
,CHOICE_NAME
,vsModeChoiceNames
);
217 SECTION_COLORS
.Load(sType
,SECTION_COLORS_NAME
,NUM_SECTION_COLORS
);
220 MusicWheel
::~MusicWheel()
222 FOREACH( MusicWheelItem
*, m_MusicWheelItems
, i
)
224 m_MusicWheelItems
.clear();
227 /* If a song or course is set in GAMESTATE and available, select it. Otherwise, choose the
228 * first available song or course. Return true if an item was set, false if no items are
230 bool MusicWheel
::SelectSongOrCourse()
232 if( GAMESTATE
->m_pPreferredSong
&& SelectSong( GAMESTATE
->m_pPreferredSong
) )
234 if( GAMESTATE
->m_pCurSong
&& SelectSong( GAMESTATE
->m_pCurSong
) )
236 if( GAMESTATE
->m_pPreferredCourse
&& SelectCourse( GAMESTATE
->m_pPreferredCourse
) )
238 if( GAMESTATE
->m_pCurCourse
&& SelectCourse( GAMESTATE
->m_pCurCourse
) )
241 // Select the first selectable song based on the sort order...
242 vector
<WheelItemData
> &wiWheelItems
= m_WheelItemDatas
[GAMESTATE
->m_SortOrder
];
243 for( unsigned i
= 0; i
< wiWheelItems
.size(); i
++ )
245 if( wiWheelItems
[i
].m_pSong
)
246 return SelectSong( wiWheelItems
[i
].m_pSong
);
247 else if ( wiWheelItems
[i
].m_pCourse
)
248 return SelectCourse( wiWheelItems
[i
].m_pCourse
);
251 LOG
->Trace( "MusicWheel::MusicWheel() - No selectable songs or courses found in WheelData" );
255 bool MusicWheel
::SelectSection( const CString
& SectionName
)
258 for( i
=0; i
<m_CurWheelItemData
.size(); i
++ )
260 if( m_CurWheelItemData
[i
]->m_sText
== SectionName
)
262 m_iSelection
= i
; // select it
266 if ( i
== m_CurWheelItemData
.size() )
271 bool MusicWheel
::SelectSong( Song
*p
)
277 vector
<WheelItemData
> &from
= m_WheelItemDatas
[GAMESTATE
->m_SortOrder
];
278 for( i
=0; i
<from
.size(); i
++ )
280 if( from
[i
].m_pSong
== p
)
282 // make its group the currently expanded group
283 SetOpenGroup(from
[i
].m_sText
);
291 for( i
=0; i
<m_CurWheelItemData
.size(); i
++ )
293 if( m_CurWheelItemData
[i
]->m_pSong
== p
)
294 m_iSelection
= i
; // select it
299 bool MusicWheel
::SelectCourse( Course
*p
)
304 GAMESTATE
->m_pCurCourse
.Set( p
);
307 vector
<WheelItemData
> &from
= m_WheelItemDatas
[GAMESTATE
->m_SortOrder
];
308 for( i
=0; i
<from
.size(); i
++ )
310 if( from
[i
].m_pCourse
== p
)
312 // make its group the currently expanded group
313 SetOpenGroup(from
[i
].m_sText
);
321 for( i
=0; i
<m_CurWheelItemData
.size(); i
++ )
323 if( m_CurWheelItemData
[i
]->m_pCourse
== p
)
324 m_iSelection
= i
; // select it
330 bool MusicWheel
::SelectModeMenuItem()
332 /* Select the last-chosen option. */
333 const vector
<WheelItemData
> &from
= m_WheelItemDatas
[GAMESTATE
->m_SortOrder
];
335 for( i
=0; i
<from
.size(); i
++ )
337 const GameCommand
&gc
= from
[i
].m_Action
;
338 if( gc
.m_sName
== m_sLastModeMenuItem
)
341 if( i
== from
.size() )
344 // make its group the currently expanded group
345 SetOpenGroup( from
[i
].m_sText
);
347 for( i
=0; i
<m_CurWheelItemData
.size(); i
++ )
349 if( m_CurWheelItemData
[i
]->m_Action
.m_sName
!= m_sLastModeMenuItem
)
351 m_iSelection
= i
; // select it
358 void MusicWheel
::GetSongList(vector
<Song
*> &arraySongs
, SortOrder so
, CString sPreferredGroup
)
360 vector
<Song
*> apAllSongs
;
361 // if( so==SORT_PREFERRED && GAMESTATE->m_sPreferredGroup!=GROUP_ALL_MUSIC)
362 // SONGMAN->GetSongs( apAllSongs, GAMESTATE->m_sPreferredGroup, GAMESTATE->GetNumStagesLeft() );
364 // SONGMAN->GetSongs( apAllSongs, GAMESTATE->GetNumStagesLeft() );
365 if( so
== SORT_POPULARITY
)
366 SONGMAN
->GetBestSongs( apAllSongs
, GAMESTATE
->m_sPreferredSongGroup
, GAMESTATE
->GetNumStagesLeft() );
368 SONGMAN
->GetSongs( apAllSongs
, GAMESTATE
->m_sPreferredSongGroup
, GAMESTATE
->GetNumStagesLeft() );
370 // copy only songs that have at least one Steps for the current GameMode
371 for( unsigned i
=0; i
<apAllSongs
.size(); i
++ )
373 Song
* pSong
= apAllSongs
[i
];
375 /* If we're on an extra stage, and this song is selected, ignore #SELECTABLE. */
376 if( pSong
!= GAMESTATE
->m_pCurSong
||
377 (!GAMESTATE
->IsExtraStage() && !GAMESTATE
->IsExtraStage2()) )
379 /* Hide songs that asked to be hidden via #SELECTABLE. */
380 if( so
!=SORT_ROULETTE
&& !pSong
->NormallyDisplayed() )
382 if( so
!=SORT_ROULETTE
&& UNLOCKMAN
->SongIsRouletteOnly( pSong
) )
384 /* Don't show in roulette if #SELECTABLE:NO. */
385 if( so
==SORT_ROULETTE
&& !pSong
->RouletteDisplayed() )
389 /* Hide locked songs. If RANDOM_PICKS_LOCKED_SONGS, hide in Roulette and Random,
391 if( (so
!=SORT_ROULETTE
|| !RANDOM_PICKS_LOCKED_SONGS
) && UNLOCKMAN
->SongIsLocked(pSong
) )
394 // If the song has at least one steps, add it.
395 if( pSong
->HasStepsType(GAMESTATE
->GetCurrentStyle()->m_StepsType
) )
396 arraySongs
.push_back( pSong
);
399 /* Hack: Add extra stage item if it was eliminated for any reason (eg. it's a long
401 if( GAMESTATE
->IsExtraStage() || GAMESTATE
->IsExtraStage2() )
407 SONGMAN
->GetExtraStageInfo( GAMESTATE
->IsExtraStage2(), GAMESTATE
->GetCurrentStyle(), pSong
, pSteps
, po
, so
);
409 if( find( arraySongs
.begin(), arraySongs
.end(), pSong
) == arraySongs
.end() )
410 arraySongs
.push_back( pSong
);
417 void MusicWheel
::BuildWheelItemDatas( vector
<WheelItemData
> &arrayWheelItemDatas
, SortOrder so
)
423 arrayWheelItemDatas
.clear(); // clear out the previous wheel items
424 vector
<CString
> vsNames
;
425 split( MODE_MENU_CHOICE_NAMES
, ",", vsNames
);
426 for( unsigned i
=0; i
<vsNames
.size(); ++i
)
428 WheelItemData
wid( TYPE_SORT
, NULL
, "", NULL
, SORT_MENU_COLOR
);
429 wid
.m_sLabel
= vsNames
[i
];
430 wid
.m_Action
.Load( i
, ParseCommands(CHOICE
.GetValue(vsNames
[i
])) );
431 wid
.m_sLabel
= wid
.m_Action
.m_sName
;
435 case SORT_ALL_COURSES
:
436 case SORT_NONSTOP_COURSES
:
437 case SORT_ONI_COURSES
:
438 case SORT_ENDLESS_COURSES
:
439 /* Don't display course modes after the first stage. */
440 if( !GAMESTATE
->IsEventMode() && GAMESTATE
->m_iCurrentStageIndex
)
444 if( !wid
.m_Action
.IsPlayable() )
447 arrayWheelItemDatas
.push_back( wid
);
456 case SORT_POPULARITY
:
457 case SORT_TOP_GRADES
:
458 case SORT_TOP_GRADES_GROUPED
:
461 case SORT_SONG_LENGTH
:
462 case SORT_EASY_METER
:
463 case SORT_MEDIUM_METER
:
464 case SORT_HARD_METER
:
465 case SORT_CHALLENGE_METER
:
467 ///////////////////////////////////
468 // Make an array of Song*, then sort them
469 ///////////////////////////////////
470 vector
<Song
*> arraySongs
;
472 GetSongList(arraySongs
, so
, GAMESTATE
->m_sPreferredSongGroup
);
474 bool bUseSections
= true;
481 SongUtil
::SortSongPointerArrayByMeter( arraySongs
, DIFFICULTY_EASY
);
482 if( (bool)PREFSMAN
->m_bPreferredSortUsesGroups
)
483 stable_sort( arraySongs
.begin(), arraySongs
.end(), SongUtil
::CompareSongPointersByGroup
);
484 bUseSections
= false;
487 SongUtil
::SortSongPointerArrayByGroupAndTitle( arraySongs
);
488 bUseSections
= GAMESTATE
->m_sPreferredSongGroup
== GROUP_ALL_MUSIC
;
491 SongUtil
::SortSongPointerArrayByTitle( arraySongs
);
494 SongUtil
::SortSongPointerArrayByBPM( arraySongs
);
496 case SORT_POPULARITY
:
497 if( (int) arraySongs
.size() > MOST_PLAYED_SONGS_TO_SHOW
)
498 arraySongs
.erase( arraySongs
.begin()+MOST_PLAYED_SONGS_TO_SHOW
, arraySongs
.end() );
499 bUseSections
= false;
501 case SORT_TOP_GRADES
:
502 case SORT_TOP_GRADES_GROUPED
:
503 SongUtil
::SortSongPointerArrayByGrade( arraySongs
);
506 SongUtil
::SortSongPointerArrayByArtist( arraySongs
);
509 SongUtil
::SortSongPointerArrayByGenre( arraySongs
);
511 case SORT_SONG_LENGTH
:
512 SongUtil
::SortSongPointerArrayBySongLength( arraySongs
);
514 case SORT_EASY_METER
:
515 SongUtil
::SortSongPointerArrayByMeter( arraySongs
, DIFFICULTY_EASY
);
517 case SORT_MEDIUM_METER
:
518 SongUtil
::SortSongPointerArrayByMeter( arraySongs
, DIFFICULTY_MEDIUM
);
520 case SORT_HARD_METER
:
521 SongUtil
::SortSongPointerArrayByMeter( arraySongs
, DIFFICULTY_HARD
);
523 case SORT_CHALLENGE_METER
:
524 SongUtil
::SortSongPointerArrayByMeter( arraySongs
, DIFFICULTY_CHALLENGE
);
527 ASSERT(0); // unhandled SortOrder
531 ///////////////////////////////////
532 // Build an array of WheelItemDatas from the sorted list of Song*'s
533 ///////////////////////////////////
534 arrayWheelItemDatas
.clear(); // clear out the previous wheel items
535 arrayWheelItemDatas
.reserve( arraySongs
.size() );
537 switch( PREFSMAN
->m_MusicWheelUsesSections
)
539 case PrefsManager
::NEVER
:
540 bUseSections
= false;
542 case PrefsManager
::ABC_ONLY
:
543 if( so
!= SORT_TITLE
&& so
!= SORT_GROUP
)
544 bUseSections
= false;
550 // Sorting twice isn't necessary. Instead, modify the compatator functions
551 // in Song.cpp to have the desired effect. -Chris
552 /* Keeping groups together with the sorts is tricky and brittle; we
553 * keep getting OTHER split up without this. However, it puts the
554 * Grade and BPM sorts in the wrong order, and they're already correct,
555 * so don't re-sort for them. */
556 // /* We're using sections, so use the section name as the top-level
558 if( so
!= SORT_TOP_GRADES
&& so
!= SORT_TOP_GRADES_GROUPED
&& so
!= SORT_BPM
)
559 SongUtil
::SortSongPointerArrayBySectionName(arraySongs
, so
);
561 // make WheelItemDatas with sections
562 CString sLastSection
= "";
563 int iSectionColorIndex
= 0;
564 for( unsigned i
=0; i
< arraySongs
.size(); i
++ )
566 Song
* pSong
= arraySongs
[i
];
567 CString sThisSection
= SongUtil
::GetSectionNameFromSongAndSort( pSong
, so
);
569 if( sThisSection
!= sLastSection
) // new section, make a section item
571 RageColor colorSection
= (so
==SORT_GROUP
) ? SONGMAN
->GetSongGroupColor(pSong
->m_sGroupName
) : SECTION_COLORS
.GetValue(iSectionColorIndex
);
572 iSectionColorIndex
= (iSectionColorIndex
+1) % NUM_SECTION_COLORS
;
573 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_SECTION
, NULL
, sThisSection
, NULL
, colorSection
) );
574 sLastSection
= sThisSection
;
577 arrayWheelItemDatas
.push_back( WheelItemData( TYPE_SONG
, pSong
, sThisSection
, NULL
, SONGMAN
->GetSongColor(pSong
)) );
582 for( unsigned i
=0; i
<arraySongs
.size(); i
++ )
584 Song
* pSong
= arraySongs
[i
];
585 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_SONG
, pSong
, "", NULL
, SONGMAN
->GetSongColor(pSong
)) );
589 if( so
!= SORT_ROULETTE
)
591 /* Only add TYPE_PORTAL if there's at least one song on the list. */
592 bool bFoundAnySong
= false;
593 for( unsigned i
=0; !bFoundAnySong
&& i
< arrayWheelItemDatas
.size(); i
++ )
594 if( arrayWheelItemDatas
[i
].m_Type
== TYPE_SONG
)
595 bFoundAnySong
= true;
596 /* Toggle to swap Random and Roulette. */
597 if (SWAP_RANDOM_AND_ROULETTE
) // Random first, like in DDR
600 if( SHOW_RANDOM
&& bFoundAnySong
)
601 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_RANDOM
, NULL
, "", NULL
, RageColor(1,0,0,1)) );
603 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_ROULETTE
, NULL
, "", NULL
, RageColor(1,0,0,1)) );
605 else // Roulette first, normal SM behavior
608 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_ROULETTE
, NULL
, "", NULL
, RageColor(1,0,0,1)) );
609 if( SHOW_RANDOM
&& bFoundAnySong
)
610 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_RANDOM
, NULL
, "", NULL
, RageColor(1,0,0,1)) );
612 if( SHOW_PORTAL
&& bFoundAnySong
)
613 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_PORTAL
, NULL
, "", NULL
, RageColor(1,0,0,1)) );
616 if( GAMESTATE
->IsExtraStage() || GAMESTATE
->IsExtraStage2() )
622 SONGMAN
->GetExtraStageInfo( GAMESTATE
->IsExtraStage2(), GAMESTATE
->GetCurrentStyle(), pSong
, pSteps
, po
, so
);
624 for( unsigned i
=0; i
<arrayWheelItemDatas
.size(); i
++ )
626 if( arrayWheelItemDatas
[i
].m_pSong
== pSong
)
628 /* Change the song color. */
629 arrayWheelItemDatas
[i
].m_color
= SONG_REAL_EXTRA_COLOR
;
636 case SORT_ALL_COURSES
:
637 case SORT_NONSTOP_COURSES
:
638 case SORT_ONI_COURSES
:
639 case SORT_ENDLESS_COURSES
:
641 vector
<Course
*> apCourses
;
644 case SORT_NONSTOP_COURSES
:
645 SONGMAN
->GetCourses( COURSE_TYPE_NONSTOP
, apCourses
, PREFSMAN
->m_bAutogenGroupCourses
);
647 case SORT_ONI_COURSES
:
648 SONGMAN
->GetCourses( COURSE_TYPE_ONI
, apCourses
, PREFSMAN
->m_bAutogenGroupCourses
);
649 SONGMAN
->GetCourses( COURSE_TYPE_SURVIVAL
, apCourses
, PREFSMAN
->m_bAutogenGroupCourses
);
651 case SORT_ENDLESS_COURSES
:
652 SONGMAN
->GetCourses( COURSE_TYPE_ENDLESS
, apCourses
, PREFSMAN
->m_bAutogenGroupCourses
);
654 case SORT_ALL_COURSES
:
655 SONGMAN
->GetAllCourses( apCourses
, PREFSMAN
->m_bAutogenGroupCourses
);
657 default: ASSERT(0); break;
660 if( PREFSMAN
->m_CourseSortOrder
== PrefsManager
::COURSE_SORT_SONGS
)
662 CourseUtil
::SortCoursePointerArrayByDifficulty( apCourses
);
666 switch( PREFSMAN
->m_CourseSortOrder
)
668 case PrefsManager
::COURSE_SORT_METER
:
669 CourseUtil
::SortCoursePointerArrayByAvgDifficulty( apCourses
);
671 case PrefsManager
::COURSE_SORT_METER_SUM
:
672 CourseUtil
::SortCoursePointerArrayByTotalDifficulty( apCourses
);
674 case PrefsManager
::COURSE_SORT_RANK
:
675 CourseUtil
::SortCoursePointerArrayByRanking( apCourses
);
680 // since we can't agree, make it an option
681 if( PREFSMAN
->m_bMoveRandomToEnd
)
682 CourseUtil
::MoveRandomToEnd( apCourses
);
685 if( so
== SORT_ALL_COURSES
)
686 CourseUtil
::SortCoursePointerArrayByType( apCourses
);
688 arrayWheelItemDatas
.clear(); // clear out the previous wheel items
690 CString sLastSection
= "";
691 int iSectionColorIndex
= 0;
692 for( unsigned i
=0; i
<apCourses
.size(); i
++ ) // foreach course
694 Course
* pCourse
= apCourses
[i
];
696 // if unlocks are on, make sure it is unlocked
697 if ( UNLOCKMAN
->CourseIsLocked(pCourse
) )
700 CString sThisSection
= "";
701 if( so
== SORT_ALL_COURSES
)
703 switch( pCourse
->GetPlayMode() )
705 case PLAY_MODE_ONI
: sThisSection
= "Oni"; break;
706 case PLAY_MODE_NONSTOP
: sThisSection
= "Nonstop"; break;
707 case PLAY_MODE_ENDLESS
: sThisSection
= "Endless"; break;
711 // check that this course has at least one song playable in the current style
712 if( !pCourse
->IsPlayableIn(GAMESTATE
->GetCurrentStyle()->m_StepsType
) )
715 if( sThisSection
!= sLastSection
) // new section, make a section item
717 RageColor c
= SECTION_COLORS
.GetValue(iSectionColorIndex
);
718 iSectionColorIndex
= (iSectionColorIndex
+1) % NUM_SECTION_COLORS
;
719 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_SECTION
, NULL
, sThisSection
, NULL
, c
) );
720 sLastSection
= sThisSection
;
723 RageColor c
= ( pCourse
->m_sGroupName
.size() == 0 ) ? pCourse
->GetColor() : pCourse
->GetColor() * SONGMAN
->GetCourseColor(pCourse
);
724 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_COURSE
, NULL
, sThisSection
, pCourse
, c
) );
730 // init music status icons
731 for( unsigned i
=0; i
<arrayWheelItemDatas
.size(); i
++ )
733 Song
* pSong
= arrayWheelItemDatas
[i
].m_pSong
;
737 WheelItemData
& WID
= arrayWheelItemDatas
[i
];
738 StepsType curStepsType
= GAMESTATE
->GetCurrentStyle()->m_StepsType
;
739 WID
.m_Flags
.bHasBeginnerOr1Meter
= pSong
->IsEasy( curStepsType
);
740 WID
.m_Flags
.bEdits
= pSong
->HasEdits( curStepsType
);
741 WID
.m_Flags
.iStagesForSong
= SongManager
::GetNumStagesForSong( pSong
);
745 if( so
== SORT_POPULARITY
)
748 for( unsigned i
=0; i
< min(3u,arrayWheelItemDatas
.size()); i
++ )
750 WheelItemData
& WID
= arrayWheelItemDatas
[i
];
751 WID
.m_Flags
.iPlayersBestNumber
= i
+1;
755 if( arrayWheelItemDatas
.empty() )
757 arrayWheelItemDatas
.push_back( WheelItemData(TYPE_SECTION
, NULL
, "- EMPTY -", NULL
, RageColor(1,0,0,1)) );
761 void MusicWheel
::RebuildAllMusicWheelItems()
763 RebuildMusicWheelItems( INT_MAX
);
766 void MusicWheel
::RebuildMusicWheelItems( int dist
)
768 // rewind to first index that will be displayed;
769 int iFirstVisibleIndex
= m_iSelection
;
770 if( m_iSelection
> int(m_CurWheelItemData
.size()-1) )
773 // find the first wheel item shown
774 iFirstVisibleIndex
-= NUM_WHEEL_ITEMS
/2;
776 ASSERT(m_CurWheelItemData
.size());
777 wrap( iFirstVisibleIndex
, m_CurWheelItemData
.size() );
779 // iIndex is now the index of the lowest WheelItem to draw
781 if( dist
== INT_MAX
)
784 for( int i
=0; i
<NUM_WHEEL_ITEMS
; i
++ )
786 int iIndex
= iFirstVisibleIndex
+ i
;
787 wrap( iIndex
, m_CurWheelItemData
.size() );
789 WheelItemData
*data
= m_CurWheelItemData
[iIndex
];
790 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
792 bool bExpanded
= (data
->m_Type
== TYPE_SECTION
) ?
(data
->m_sText
== m_sExpandedSectionName
) : false;
793 display
->LoadFromWheelItemData( data
, bExpanded
);
798 // Shift items and refresh only those that have changed.
799 CircularShift( m_MusicWheelItems
, dist
);
802 for( int i
=NUM_WHEEL_ITEMS
-dist
; i
<NUM_WHEEL_ITEMS
; i
++ )
804 int iIndex
= iFirstVisibleIndex
+ i
;
805 wrap( iIndex
, m_CurWheelItemData
.size() );
807 WheelItemData
*data
= m_CurWheelItemData
[iIndex
];
808 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
810 bool bExpanded
= (data
->m_Type
== TYPE_SECTION
) ?
(data
->m_sText
== m_sExpandedSectionName
) : false;
811 display
->LoadFromWheelItemData( data
, bExpanded
);
816 for( int i
=0; i
<-dist
; i
++ )
818 int iIndex
= iFirstVisibleIndex
+ i
;
819 wrap( iIndex
, m_CurWheelItemData
.size() );
821 WheelItemData
*data
= m_CurWheelItemData
[iIndex
];
822 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
824 bool bExpanded
= (data
->m_Type
== TYPE_SECTION
) ?
(data
->m_sText
== m_sExpandedSectionName
) : false;
825 display
->LoadFromWheelItemData( data
, bExpanded
);
831 void MusicWheel
::NotesOrTrailChanged( PlayerNumber pn
) // update grade graphics and top score
833 for( int i
=0; i
<NUM_WHEEL_ITEMS
; i
++ )
835 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
836 display
->RefreshGrades();
840 void MusicWheel
::DrawItem( int i
)
842 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
844 const float fThisBannerPositionOffsetFromSelection
= i
- NUM_WHEEL_ITEMS
/2 + m_fPositionOffsetFromSelection
;
845 if( fabsf(fThisBannerPositionOffsetFromSelection
) > NUM_WHEEL_ITEMS_TO_DRAW
/2 )
848 switch( m_WheelState
)
850 case STATE_SELECTING_MUSIC
:
851 case STATE_ROULETTE_SPINNING
:
852 case STATE_ROULETTE_SLOWING_DOWN
:
853 case STATE_RANDOM_SPINNING
:
855 SetItemPosition( *display
, fThisBannerPositionOffsetFromSelection
);
860 WheelBase
::DrawItem(i
, display
, fThisBannerPositionOffsetFromSelection
);
863 bool MusicWheel
::IsSettled() const
867 if( m_WheelState
!= STATE_SELECTING_MUSIC
&& m_WheelState
!= STATE_LOCKED
)
869 if( m_fPositionOffsetFromSelection
!= 0 )
875 void MusicWheel
::UpdateItems(float fDeltaTime
)
877 for( unsigned i
=0; i
<unsigned(NUM_WHEEL_ITEMS
); i
++ )
879 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
881 display
->Update( fDeltaTime
);
885 void MusicWheel
::UpdateSwitch()
887 switch( m_WheelState
)
889 case STATE_FLYING_OFF_BEFORE_NEXT_SORT
:
891 Song
* pPrevSelectedSong
= m_CurWheelItemData
[m_iSelection
]->m_pSong
;
893 SCREENMAN
->PostMessageToTopScreen( SM_SortOrderChanged
, 0 );
895 SetOpenGroup(SongUtil
::GetSectionNameFromSongAndSort( pPrevSelectedSong
, GAMESTATE
->m_SortOrder
));
900 // Select the previously selected item
902 switch( GAMESTATE
->m_SortOrder
)
905 // Look for the last selected song or course
906 SelectSongOrCourse();
909 SelectModeMenuItem();
914 // Change difficulty for sorts by meter - XXX: do this with GameCommand?
916 Difficulty dc
= DIFFICULTY_INVALID
;
917 switch( GAMESTATE
->m_SortOrder
)
919 case SORT_EASY_METER
: dc
= DIFFICULTY_EASY
; break;
920 case SORT_MEDIUM_METER
: dc
= DIFFICULTY_MEDIUM
; break;
921 case SORT_HARD_METER
: dc
= DIFFICULTY_HARD
; break;
922 case SORT_CHALLENGE_METER
: dc
= DIFFICULTY_CHALLENGE
; break;
924 if( dc
!= DIFFICULTY_INVALID
)
926 FOREACH_PlayerNumber( p
)
927 if( GAMESTATE
->IsPlayerEnabled(p
) )
928 GAMESTATE
->m_PreferredDifficulty
[p
].Set( dc
);
931 SCREENMAN
->PostMessageToTopScreen( SM_SongChanged
, 0 );
932 RebuildAllMusicWheelItems();
934 m_WheelState
= STATE_FLYING_ON_AFTER_NEXT_SORT
;
936 SCREENMAN
->ZeroNextUpdate();
940 case STATE_FLYING_ON_AFTER_NEXT_SORT
:
941 m_WheelState
= STATE_SELECTING_MUSIC
; // now, wait for input
944 case STATE_TWEENING_ON_SCREEN
:
945 m_fTimeLeftInState
= 0;
946 // if( (GAMESTATE->IsExtraStage() && !PREFSMAN->m_bPickExtraStage) || GAMESTATE->IsExtraStage2() )
947 if( GAMESTATE
->IsExtraStage2() )
949 m_WheelState
= STATE_LOCKED
;
950 SCREENMAN
->PlayStartSound();
951 m_fLockedWheelVelocity
= 0;
955 m_WheelState
= STATE_SELECTING_MUSIC
;
958 case STATE_TWEENING_OFF_SCREEN
:
959 m_WheelState
= STATE_WAITING_OFF_SCREEN
;
960 m_fTimeLeftInState
= 0;
962 case STATE_SELECTING_MUSIC
:
963 m_fTimeLeftInState
= 0;
965 case STATE_ROULETTE_SPINNING
:
966 case STATE_RANDOM_SPINNING
:
968 case STATE_WAITING_OFF_SCREEN
:
972 case STATE_ROULETTE_SLOWING_DOWN
:
973 if( m_iSwitchesLeftInSpinDown
== 0 )
975 m_WheelState
= STATE_LOCKED
;
976 m_fTimeLeftInState
= 0;
977 SCREENMAN
->PlayStartSound();
978 m_fLockedWheelVelocity
= 0;
980 /* Send this again so the screen starts sample music. */
981 SCREENMAN
->PostMessageToTopScreen( SM_SongChanged
, 0 );
985 m_iSwitchesLeftInSpinDown
--;
986 const float SwitchTimes
[] = { 0.5f
, 1.3f
, 0.8f
, 0.4f
, 0.2f
};
987 ASSERT(m_iSwitchesLeftInSpinDown
>= 0 && m_iSwitchesLeftInSpinDown
<= 4);
988 m_fTimeLeftInState
= SwitchTimes
[m_iSwitchesLeftInSpinDown
];
990 LOG
->Trace( "m_iSwitchesLeftInSpinDown id %d, m_fTimeLeftInState is %f", m_iSwitchesLeftInSpinDown
, m_fTimeLeftInState
);
992 if( m_iSwitchesLeftInSpinDown
< 2 )
993 ChangeMusic(randomf(0,1) >= 0.5f?
1:-1);
999 ASSERT(0); // all state changes should be handled explicitly
1004 void MusicWheel
::ChangeMusic(int dist
)
1006 m_iSelection
+= dist
;
1007 wrap( m_iSelection
, m_CurWheelItemData
.size() );
1009 RebuildMusicWheelItems( dist
);
1011 m_fPositionOffsetFromSelection
+= dist
;
1013 SCREENMAN
->PostMessageToTopScreen( SM_SongChanged
, 0 );
1015 /* If we're moving automatically, don't play this; it'll be called in Update.*/
1017 m_soundChangeMusic
.Play();
1021 bool MusicWheel
::ChangeSort( SortOrder new_so
) // return true if change successful
1023 ASSERT( new_so
< NUM_SORT_ORDERS
);
1024 if( GAMESTATE
->m_SortOrder
== new_so
)
1027 /* Don't change to SORT_MODE_MENU if it doesn't have at least two choices. */
1028 if( new_so
== SORT_MODE_MENU
&& m_WheelItemDatas
[new_so
].size() < 2 )
1031 switch( m_WheelState
)
1033 case STATE_SELECTING_MUSIC
:
1034 case STATE_FLYING_ON_AFTER_NEXT_SORT
:
1035 break; // fall through
1037 return false; // don't continue
1040 SCREENMAN
->PostMessageToTopScreen( SM_SortOrderChanging
, 0 );
1042 m_soundChangeSort
.Play();
1044 TweenOffScreen(true);
1046 /* Save the new preference. */
1047 if( IsSongSort(new_so
) )
1048 GAMESTATE
->m_PreferredSortOrder
= new_so
;
1049 GAMESTATE
->m_SortOrder
= new_so
;
1051 GAMESTATE
->m_SortOrder
= new_so
;
1053 m_WheelState
= STATE_FLYING_OFF_BEFORE_NEXT_SORT
;
1057 bool MusicWheel
::NextSort() // return true if change successful
1059 // don't allow NextSort when on the sort menu or mode menu
1060 if( GAMESTATE
->m_SortOrder
== SORT_MODE_MENU
)
1063 // find the index of the current sort
1065 while( cur
< int(SONG_SORT_ORDERS
.size()) && SONG_SORT_ORDERS
[cur
] != GAMESTATE
->m_SortOrder
)
1068 // move to the next sort with wrapping
1070 wrap( cur
, SONG_SORT_ORDERS
.size() );
1073 SortOrder soNew
= SONG_SORT_ORDERS
[cur
];
1074 return ChangeSort( soNew
);
1077 bool MusicWheel
::Select() // return true if this selection ends the screen
1079 LOG
->Trace( "MusicWheel::Select()" );
1081 switch( m_WheelState
)
1083 case STATE_FLYING_OFF_BEFORE_NEXT_SORT
:
1084 case STATE_ROULETTE_SLOWING_DOWN
:
1090 if( m_WheelState
== STATE_ROULETTE_SPINNING
)
1092 m_WheelState
= STATE_ROULETTE_SLOWING_DOWN
;
1093 m_iSwitchesLeftInSpinDown
= ROULETTE_SLOW_DOWN_SWITCHES
/2+1 + rand()%(ROULETTE_SLOW_DOWN_SWITCHES
/2);
1094 m_fTimeLeftInState
= 0.1f
;
1099 if( m_WheelState
== STATE_RANDOM_SPINNING
)
1101 m_fPositionOffsetFromSelection
= max(m_fPositionOffsetFromSelection
, 0.3f
);
1102 m_WheelState
= STATE_LOCKED
;
1103 SCREENMAN
->PlayStartSound();
1104 m_fLockedWheelVelocity
= 0;
1105 SCREENMAN
->PostMessageToTopScreen( SM_SongChanged
, 0 );
1109 switch( m_CurWheelItemData
[m_iSelection
]->m_Type
)
1113 CString sThisItemSectionName
= m_CurWheelItemData
[m_iSelection
]->m_sText
;
1114 if( m_sExpandedSectionName
== sThisItemSectionName
) // already expanded
1115 m_sExpandedSectionName
= ""; // collapse it
1116 else // already collapsed
1117 m_sExpandedSectionName
= sThisItemSectionName
; // expand it
1119 m_soundExpand
.Play();
1121 SetOpenGroup(m_sExpandedSectionName
);
1132 // Don't -permanently- unlock the song. Just let them play
1133 // the unlocked song once.
1134 // if( !GAMESTATE->IsExtraStage() && !GAMESTATE->IsExtraStage2() )
1135 // UNLOCKMAN->UnlockSong( m_CurWheelItemData[m_iSelection]->m_pSong );
1140 LOG
->Trace("New sort order selected: %s - %s",
1141 m_CurWheelItemData
[m_iSelection
]->m_sLabel
.c_str(),
1142 SortOrderToString(m_CurWheelItemData
[m_iSelection
]->m_Action
.m_SortOrder
).c_str() );
1143 m_CurWheelItemData
[m_iSelection
]->m_Action
.ApplyToAllPlayers();
1144 ChangeSort( GAMESTATE
->m_PreferredSortOrder
);
1145 m_sLastModeMenuItem
= m_CurWheelItemData
[m_iSelection
]->m_Action
.m_sName
;
1153 void MusicWheel
::StartRoulette()
1155 MESSAGEMAN
->Broadcast("StartRoulette");
1156 m_WheelState
= STATE_ROULETTE_SPINNING
;
1158 m_TimeBeforeMovingBegins
= 0;
1159 m_SpinSpeed
= 1.0f
/ROULETTE_SWITCH_SECONDS
;
1160 SetOpenGroup("", SORT_ROULETTE
);
1163 void MusicWheel
::StartRandom()
1165 MESSAGEMAN
->Broadcast("StartRandom");
1166 /* If RANDOM_PICKS_LOCKED_SONGS is disabled, pick a song from the active sort and
1167 * section. If enabled, picking from the section makes it too easy to trick the
1168 * game into picking a locked song, so pick from SORT_ROULETTE. */
1169 if( RANDOM_PICKS_LOCKED_SONGS
)
1171 /* Shuffle and use the roulette wheel. */
1173 random_shuffle( m_WheelItemDatas
[SORT_ROULETTE
].begin(), m_WheelItemDatas
[SORT_ROULETTE
].end(), rnd
);
1174 SetOpenGroup( "", SORT_ROULETTE
);
1178 SetOpenGroup( "", GAMESTATE
->m_PreferredSortOrder
);
1182 m_TimeBeforeMovingBegins
= 0;
1183 m_SpinSpeed
= 1.0f
/ROULETTE_SWITCH_SECONDS
;
1184 m_SpinSpeed
*= 20.0f
; /* faster! */
1185 m_WheelState
= STATE_RANDOM_SPINNING
;
1187 SelectSong( GetPreferredSelectionForRandomOrPortal() );
1190 RebuildAllMusicWheelItems();
1193 void MusicWheel
::SetOpenGroup(CString group
, SortOrder so
)
1195 if( so
!= SORT_INVALID
)
1196 GAMESTATE
->m_SortOrder
= so
;
1198 m_sExpandedSectionName
= group
;
1200 WheelItemData
*old
= NULL
;
1201 if(!m_CurWheelItemData
.empty())
1202 old
= m_CurWheelItemData
[m_iSelection
];
1204 m_CurWheelItemData
.clear();
1205 vector
<WheelItemData
> &from
= m_WheelItemDatas
[GAMESTATE
->m_SortOrder
];
1206 for( unsigned i
= 0; i
< from
.size(); ++i
)
1208 WheelItemData
&d
= from
[i
];
1209 if( (d
.m_Type
== TYPE_SONG
|| d
.m_Type
== TYPE_COURSE
) &&
1210 !d
.m_sText
.empty() &&
1211 d
.m_sText
!= group
)
1214 /* Only show tutorial songs in arcade */
1215 if( GAMESTATE
->m_PlayMode
!=PLAY_MODE_REGULAR
&&
1217 d
.m_pSong
->IsTutorial() )
1220 m_CurWheelItemData
.push_back(&d
);
1225 // Try to select the item that was selected before changing groups
1229 for( unsigned i
=0; i
<m_CurWheelItemData
.size(); i
++ )
1231 if( m_CurWheelItemData
[i
] == old
)
1238 RebuildAllMusicWheelItems();
1241 bool MusicWheel
::IsRouletting() const
1243 return m_WheelState
== STATE_ROULETTE_SPINNING
|| m_WheelState
== STATE_ROULETTE_SLOWING_DOWN
||
1244 m_WheelState
== STATE_RANDOM_SPINNING
;
1247 void MusicWheel
::TweenOnScreenUpdateItems(bool changing_sort
) {
1248 for( int i
=0; i
<NUM_WHEEL_ITEMS
; i
++ )
1250 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
1251 float fThisBannerPositionOffsetFromSelection
= i
- NUM_WHEEL_ITEMS
/2 + m_fPositionOffsetFromSelection
;
1252 SetItemPosition( *display
, fThisBannerPositionOffsetFromSelection
);
1254 COMMAND( display
, "StartOn");
1255 const float delay
= fabsf(i
-WHEEL_ITEM_ON_DELAY_CENTER
) * WHEEL_ITEM_ON_DELAY_OFFSET
;
1256 display
->BeginTweening( delay
); // sleep
1257 COMMAND( display
, "FinishOn");
1259 display
->HurryTweening( 0.25f
);
1263 void MusicWheel
::TweenOffScreenUpdateItems(bool changing_sort
) {
1264 for( int i
=0; i
<NUM_WHEEL_ITEMS
; i
++ )
1266 MusicWheelItem
*display
= m_MusicWheelItems
[i
];
1267 float fThisBannerPositionOffsetFromSelection
= i
- NUM_WHEEL_ITEMS
/2 + m_fPositionOffsetFromSelection
;
1268 SetItemPosition( *display
, fThisBannerPositionOffsetFromSelection
);
1270 COMMAND( display
, "StartOff");
1271 const float delay
= fabsf(i
-WHEEL_ITEM_OFF_DELAY_CENTER
) * WHEEL_ITEM_OFF_DELAY_OFFSET
;
1272 display
->BeginTweening( delay
); // sleep
1273 COMMAND( display
, "FinishOff");
1275 display
->HurryTweening( 0.25f
);
1279 bool MusicWheel
::MoveSpecific(int n
)
1281 /* If we're not selecting, discard this. We won't ignore it; we'll
1282 * get called again every time the key is repeated. */
1283 /* Still process Move(0) so we sometimes continue moving immediate
1284 * after the sort change finished and before the repeat event causes a
1285 * Move(0). -Chris */
1286 switch( m_WheelState
)
1288 case STATE_SELECTING_MUSIC
:
1290 case STATE_FLYING_OFF_BEFORE_NEXT_SORT
:
1291 case STATE_FLYING_ON_AFTER_NEXT_SORT
:
1296 return false; // don't continue
1299 if(m_Moving
!= 0 && n
== 0 && m_TimeBeforeMovingBegins
== 0)
1301 /* We were moving, and now we're stopping. If we're really close to
1302 * the selection, move to the next one, so we have a chance to spin down
1304 if(fabsf(m_fPositionOffsetFromSelection
) < 0.25f
)
1305 ChangeMusic(m_Moving
);
1307 /* Make sure the user always gets an SM_SongChanged when
1308 * Moving() is 0, so the final banner, etc. always gets set. */
1309 SCREENMAN
->PostMessageToTopScreen( SM_SongChanged
, 0 );
1314 Song
* MusicWheel
::GetSelectedSong()
1316 switch( m_CurWheelItemData
[m_iSelection
]->m_Type
)
1319 return GetPreferredSelectionForRandomOrPortal();
1322 return m_CurWheelItemData
[m_iSelection
]->m_pSong
;
1325 /* Find a random song. If possible, find one that has the preferred difficulties of
1326 * each player. Prefer songs in the active group, if any.
1328 * Note that if this is called, we *must* find a song. We will only be called if
1329 * the active sort has at least one song, but there may be no open group. This means
1330 * that any filters and preferences applied here must be optional. */
1331 Song
*MusicWheel
::GetPreferredSelectionForRandomOrPortal()
1333 // probe to find a song that has the preferred
1334 // difficulties of each player
1335 vector
<Difficulty
> vDifficultiesToRequire
;
1336 FOREACH_HumanPlayer(p
)
1338 if( GAMESTATE
->m_PreferredDifficulty
[p
] == DIFFICULTY_INVALID
)
1341 // TRICKY: Don't require that edits be present if perferred
1342 // difficulty is DIFFICULTY_EDIT. Otherwise, players could use this
1343 // to set up a 100% chance of getting a particular locked song by
1344 // having a single edit for a locked song.
1345 if( GAMESTATE
->m_PreferredDifficulty
[p
] == DIFFICULTY_EDIT
)
1348 vDifficultiesToRequire
.push_back( GAMESTATE
->m_PreferredDifficulty
[p
] );
1351 CString sPreferredGroup
= m_sExpandedSectionName
;
1352 vector
<WheelItemData
> &wid
= m_WheelItemDatas
[GAMESTATE
->m_SortOrder
];
1354 StepsType st
= GAMESTATE
->GetCurrentStyle()->m_StepsType
;
1356 #define NUM_PROBES 1000
1357 for( int i
=0; i
<NUM_PROBES
; i
++ )
1359 /* Maintaining difficulties is higher priority than maintaining the current
1361 if( i
== NUM_PROBES
/4 )
1362 sPreferredGroup
= "";
1363 if( i
== NUM_PROBES
/2 )
1364 vDifficultiesToRequire
.clear();
1366 int iSelection
= rand() % wid
.size();
1367 if( wid
[iSelection
].m_Type
!= TYPE_SONG
)
1370 Song
* pSong
= wid
[iSelection
].m_pSong
;
1373 if( pSong
->IsCustomSong() )
1376 if( !sPreferredGroup
.empty() && wid
[iSelection
].m_sText
!= sPreferredGroup
)
1379 // There's an off possibility that somebody might have only one song with only beginner steps.
1380 if( i
< 900 && pSong
->IsTutorial() )
1383 FOREACH( Difficulty
, vDifficultiesToRequire
, d
)
1384 if( !pSong
->HasStepsTypeAndDifficulty(st
,*d
) )
1386 return wid
[iSelection
].m_pSong
;
1390 LOG
->Warn( "Couldn't find any songs" );
1391 return wid
[0].m_pSong
;
1394 void MusicWheel
::FinishChangingSorts()
1397 m_WheelState
= STATE_SELECTING_MUSIC
;
1398 m_fTimeLeftInState
= 0;
1402 * (c) 2001-2004 Chris Danford, Chris Gomez, Glenn Maynard
1403 * All rights reserved.
1405 * Permission is hereby granted, free of charge, to any person obtaining a
1406 * copy of this software and associated documentation files (the
1407 * "Software"), to deal in the Software without restriction, including
1408 * without limitation the rights to use, copy, modify, merge, publish,
1409 * distribute, and/or sell copies of the Software, and to permit persons to
1410 * whom the Software is furnished to do so, provided that the above
1411 * copyright notice(s) and this permission notice appear in all copies of
1412 * the Software and that both the above copyright notice(s) and this
1413 * permission notice appear in supporting documentation.
1415 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1416 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1417 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
1418 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
1419 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
1420 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
1421 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1422 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1423 * PERFORMANCE OF THIS SOFTWARE.