Implement TopGradesGrouped properly. Tested somewhat. Needs to be tried on larger...
[openitg.git] / src / MusicWheel.cpp
1 #include "global.h"
2 #include "MusicWheel.h"
3 #include "SongManager.h"
4 #include "ScreenManager.h" // for sending SM_PlayMusicSample
5 #include "RageLog.h"
6 #include "GameState.h"
7 #include "song.h"
8 #include "Steps.h"
9 #include "UnlockManager.h"
10 #include "ActorUtil.h"
11 #include "SongUtil.h"
12 #include "CourseUtil.h"
13 #include "Style.h"
14 #include "PlayerState.h"
15
16 #define NUM_WHEEL_ITEMS ((int)ceil(NUM_WHEEL_ITEMS_TO_DRAW+2))
17
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()); }
20
21 AutoScreenMessage( SM_SongChanged ) // TODO: Replace this with a Message and MESSAGEMAN
22 AutoScreenMessage( SM_SortOrderChanging );
23 AutoScreenMessage( SM_SortOrderChanged );
24
25 const int MAX_WHEEL_SOUND_SPEED = 15;
26
27 static const SortOrder g_SongSortOrders[] =
28 {
29 SORT_GROUP,
30 SORT_TITLE,
31 SORT_BPM,
32 SORT_POPULARITY,
33 SORT_ARTIST,
34 SORT_GENRE,
35 SORT_SONG_LENGTH
36 };
37 const vector<SortOrder> SONG_SORT_ORDERS( g_SongSortOrders, g_SongSortOrders + ARRAYLEN(g_SongSortOrders) );
38
39 MusicWheel::MusicWheel()
40 {
41 }
42
43 SortOrder ForceAppropriateSort( PlayMode pm, SortOrder so )
44 {
45 switch( pm )
46 {
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;
51 }
52
53 /* If we're not in a course mode, don't start in a course sort. */
54 switch( so )
55 {
56 case SORT_ONI_COURSES:
57 case SORT_NONSTOP_COURSES:
58 case SORT_ENDLESS_COURSES:
59 so = SORT_INVALID;
60 break;
61 }
62
63 return so;
64 }
65
66
67 void MusicWheel::Load( CString sType )
68 {
69 LOG->Trace( "MusicWheel::Load('%s')", sType.c_str() );
70
71 LoadFromMetrics( sType );
72 LoadVariables();
73
74 FOREACH( MusicWheelItem*, m_MusicWheelItems, i )
75 SAFE_DELETE( *i );
76 m_MusicWheelItems.clear();
77 for( int i=0; i<NUM_WHEEL_ITEMS; i++ )
78 m_MusicWheelItems.push_back( new MusicWheelItem );
79
80
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() );
84 else
85 LOG->Trace( "Current Song: NULL" );
86
87 SONGMAN->UpdateRankingCourses();
88
89 /*
90 // for debugging.
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;
94 */
95
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 );
99
100 m_WheelState = STATE_SELECTING_MUSIC;
101
102 if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() )
103 {
104 // make the preferred group the group of the last song played.
105 // if( GAMESTATE->m_sPreferredSongGroup==GROUP_ALL_MUSIC && !PREFSMAN->m_bPickExtraStage )
106 // {
107 // ASSERT(GAMESTATE->m_pCurSong);
108 // GAMESTATE->m_sPreferredSongGroup = GAMESTATE->m_pCurSong->m_sGroupName;
109 // }
110
111 Song* pSong;
112 Steps* pSteps;
113 PlayerOptions po;
114 SongOptions so;
115 SONGMAN->GetExtraStageInfo(
116 GAMESTATE->IsExtraStage2(),
117 GAMESTATE->GetCurrentStyle(),
118 pSong,
119 pSteps,
120 po,
121 so );
122 GAMESTATE->m_pCurSong.Set( pSong );
123 GAMESTATE->m_pPreferredSong = pSong;
124 FOREACH_HumanPlayer( p )
125 {
126 GAMESTATE->m_pCurSteps[p].Set( pSteps );
127 GAMESTATE->m_pPlayerState[p]->m_PlayerOptions = po;
128 GAMESTATE->m_PreferredDifficulty[p].Set( pSteps->GetDifficulty() );
129 }
130 GAMESTATE->m_SongOptions = so;
131 }
132
133 GAMESTATE->m_SortOrder = GAMESTATE->m_PreferredSortOrder;
134
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;
138
139 GAMESTATE->m_SortOrder = ForceAppropriateSort( GAMESTATE->m_PlayMode, GAMESTATE->m_SortOrder );
140
141 /* Only save the sort order if the player didn't already have one. If he did, don't
142 * overwrite it. */
143 if( GAMESTATE->m_PreferredSortOrder == SORT_INVALID )
144 GAMESTATE->m_PreferredSortOrder = GAMESTATE->m_SortOrder;
145
146 /* Update for SORT_MOST_PLAYED. */
147 SONGMAN->UpdateBest();
148
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();
153
154 RageTimer timer;
155 CString times;
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 )
159 {
160 BuildWheelItemDatas( m_WheelItemDatas[so], so );
161 times += ssprintf( "%i:%.3f ", so, timer.GetDeltaTime() );
162 }
163 LOG->Trace( "took: %s", times.c_str() );
164
165 /* Set m_LastModeMenuItem to the first item that matches the current mode. (Do this
166 * after building wheel item data.) */
167 {
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() )
171 {
172 m_sLastModeMenuItem = from[i].m_Action.m_sName;
173 break;
174 }
175 }
176
177
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() )
186 {
187 GAMESTATE->m_pCurSong.Set( NULL );
188 }
189
190 // Select the the previously selected song (if any)
191 if( !SelectSongOrCourse() )
192 SetOpenGroup("");
193
194 // rebuild the WheelItems that appear on screen
195 RebuildAllMusicWheelItems();
196 }
197
198 void MusicWheel::LoadFromMetrics( CString sType )
199 {
200 WheelBase::LoadFromMetrics(sType);
201
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);
218 }
219
220 MusicWheel::~MusicWheel()
221 {
222 FOREACH( MusicWheelItem*, m_MusicWheelItems, i )
223 SAFE_DELETE( *i );
224 m_MusicWheelItems.clear();
225 }
226
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
229 * available. */
230 bool MusicWheel::SelectSongOrCourse()
231 {
232 if( GAMESTATE->m_pPreferredSong && SelectSong( GAMESTATE->m_pPreferredSong ) )
233 return true;
234 if( GAMESTATE->m_pCurSong && SelectSong( GAMESTATE->m_pCurSong ) )
235 return true;
236 if( GAMESTATE->m_pPreferredCourse && SelectCourse( GAMESTATE->m_pPreferredCourse ) )
237 return true;
238 if( GAMESTATE->m_pCurCourse && SelectCourse( GAMESTATE->m_pCurCourse ) )
239 return true;
240
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++ )
244 {
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 );
249 }
250
251 LOG->Trace( "MusicWheel::MusicWheel() - No selectable songs or courses found in WheelData" );
252 return false;
253 }
254
255 bool MusicWheel::SelectSection( const CString & SectionName )
256 {
257 unsigned int i;
258 for( i=0; i<m_CurWheelItemData.size(); i++ )
259 {
260 if( m_CurWheelItemData[i]->m_sText == SectionName )
261 {
262 m_iSelection = i; // select it
263 break;
264 }
265 }
266 if ( i == m_CurWheelItemData.size() )
267 return false;
268 return true;
269 }
270
271 bool MusicWheel::SelectSong( Song *p )
272 {
273 if(p == NULL)
274 return false;
275
276 unsigned i;
277 vector<WheelItemData> &from = m_WheelItemDatas[GAMESTATE->m_SortOrder];
278 for( i=0; i<from.size(); i++ )
279 {
280 if( from[i].m_pSong == p )
281 {
282 // make its group the currently expanded group
283 SetOpenGroup(from[i].m_sText);
284 break;
285 }
286 }
287
288 if(i == from.size())
289 return false;
290
291 for( i=0; i<m_CurWheelItemData.size(); i++ )
292 {
293 if( m_CurWheelItemData[i]->m_pSong == p )
294 m_iSelection = i; // select it
295 }
296 return true;
297 }
298
299 bool MusicWheel::SelectCourse( Course *p )
300 {
301 if(p == NULL)
302 return false;
303
304 GAMESTATE->m_pCurCourse.Set( p );
305
306 unsigned i;
307 vector<WheelItemData> &from = m_WheelItemDatas[GAMESTATE->m_SortOrder];
308 for( i=0; i<from.size(); i++ )
309 {
310 if( from[i].m_pCourse == p )
311 {
312 // make its group the currently expanded group
313 SetOpenGroup(from[i].m_sText);
314 break;
315 }
316 }
317
318 if(i == from.size())
319 return false;
320
321 for( i=0; i<m_CurWheelItemData.size(); i++ )
322 {
323 if( m_CurWheelItemData[i]->m_pCourse == p )
324 m_iSelection = i; // select it
325 }
326
327 return true;
328 }
329
330 bool MusicWheel::SelectModeMenuItem()
331 {
332 /* Select the last-chosen option. */
333 const vector<WheelItemData> &from = m_WheelItemDatas[GAMESTATE->m_SortOrder];
334 unsigned i;
335 for( i=0; i<from.size(); i++ )
336 {
337 const GameCommand &gc = from[i].m_Action;
338 if( gc.m_sName == m_sLastModeMenuItem )
339 break;
340 }
341 if( i == from.size() )
342 return false;
343
344 // make its group the currently expanded group
345 SetOpenGroup( from[i].m_sText );
346
347 for( i=0; i<m_CurWheelItemData.size(); i++ )
348 {
349 if( m_CurWheelItemData[i]->m_Action.m_sName != m_sLastModeMenuItem )
350 continue;
351 m_iSelection = i; // select it
352 break;
353 }
354
355 return true;
356 }
357
358 void MusicWheel::GetSongList(vector<Song*> &arraySongs, SortOrder so, CString sPreferredGroup )
359 {
360 vector<Song*> apAllSongs;
361 // if( so==SORT_PREFERRED && GAMESTATE->m_sPreferredGroup!=GROUP_ALL_MUSIC)
362 // SONGMAN->GetSongs( apAllSongs, GAMESTATE->m_sPreferredGroup, GAMESTATE->GetNumStagesLeft() );
363 // else
364 // SONGMAN->GetSongs( apAllSongs, GAMESTATE->GetNumStagesLeft() );
365 if( so == SORT_POPULARITY )
366 SONGMAN->GetBestSongs( apAllSongs, GAMESTATE->m_sPreferredSongGroup, GAMESTATE->GetNumStagesLeft() );
367 else
368 SONGMAN->GetSongs( apAllSongs, GAMESTATE->m_sPreferredSongGroup, GAMESTATE->GetNumStagesLeft() );
369
370 // copy only songs that have at least one Steps for the current GameMode
371 for( unsigned i=0; i<apAllSongs.size(); i++ )
372 {
373 Song* pSong = apAllSongs[i];
374
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()) )
378 {
379 /* Hide songs that asked to be hidden via #SELECTABLE. */
380 if( so!=SORT_ROULETTE && !pSong->NormallyDisplayed() )
381 continue;
382 if( so!=SORT_ROULETTE && UNLOCKMAN->SongIsRouletteOnly( pSong ) )
383 continue;
384 /* Don't show in roulette if #SELECTABLE:NO. */
385 if( so==SORT_ROULETTE && !pSong->RouletteDisplayed() )
386 continue;
387 }
388
389 /* Hide locked songs. If RANDOM_PICKS_LOCKED_SONGS, hide in Roulette and Random,
390 * too. */
391 if( (so!=SORT_ROULETTE || !RANDOM_PICKS_LOCKED_SONGS) && UNLOCKMAN->SongIsLocked(pSong) )
392 continue;
393
394 // If the song has at least one steps, add it.
395 if( pSong->HasStepsType(GAMESTATE->GetCurrentStyle()->m_StepsType) )
396 arraySongs.push_back( pSong );
397 }
398
399 /* Hack: Add extra stage item if it was eliminated for any reason (eg. it's a long
400 * song). */
401 if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() )
402 {
403 Song* pSong;
404 Steps* pSteps;
405 PlayerOptions po;
406 SongOptions so;
407 SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pSong, pSteps, po, so );
408
409 if( find( arraySongs.begin(), arraySongs.end(), pSong ) == arraySongs.end() )
410 arraySongs.push_back( pSong );
411 }
412 }
413
414
415
416
417 void MusicWheel::BuildWheelItemDatas( vector<WheelItemData> &arrayWheelItemDatas, SortOrder so )
418 {
419 switch( so )
420 {
421 case SORT_MODE_MENU:
422 {
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 )
427 {
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;
432
433 switch( so )
434 {
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 )
441 continue;
442 }
443
444 if( !wid.m_Action.IsPlayable() )
445 continue;
446
447 arrayWheelItemDatas.push_back( wid );
448 }
449 break;
450 }
451 case SORT_PREFERRED:
452 case SORT_ROULETTE:
453 case SORT_GROUP:
454 case SORT_TITLE:
455 case SORT_BPM:
456 case SORT_POPULARITY:
457 case SORT_TOP_GRADES:
458 case SORT_TOP_GRADES_GROUPED:
459 case SORT_ARTIST:
460 case SORT_GENRE:
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:
466 {
467 ///////////////////////////////////
468 // Make an array of Song*, then sort them
469 ///////////////////////////////////
470 vector<Song*> arraySongs;
471
472 GetSongList(arraySongs, so, GAMESTATE->m_sPreferredSongGroup );
473
474 bool bUseSections = true;
475
476 // sort the songs
477 switch( so )
478 {
479 case SORT_PREFERRED:
480 case SORT_ROULETTE:
481 SongUtil::SortSongPointerArrayByMeter( arraySongs, DIFFICULTY_EASY );
482 if( (bool)PREFSMAN->m_bPreferredSortUsesGroups )
483 stable_sort( arraySongs.begin(), arraySongs.end(), SongUtil::CompareSongPointersByGroup );
484 bUseSections = false;
485 break;
486 case SORT_GROUP:
487 SongUtil::SortSongPointerArrayByGroupAndTitle( arraySongs );
488 bUseSections = GAMESTATE->m_sPreferredSongGroup == GROUP_ALL_MUSIC;
489 break;
490 case SORT_TITLE:
491 SongUtil::SortSongPointerArrayByTitle( arraySongs );
492 break;
493 case SORT_BPM:
494 SongUtil::SortSongPointerArrayByBPM( arraySongs );
495 break;
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;
500 break;
501 case SORT_TOP_GRADES:
502 case SORT_TOP_GRADES_GROUPED:
503 SongUtil::SortSongPointerArrayByGrade( arraySongs );
504 break;
505 case SORT_ARTIST:
506 SongUtil::SortSongPointerArrayByArtist( arraySongs );
507 break;
508 case SORT_GENRE:
509 SongUtil::SortSongPointerArrayByGenre( arraySongs );
510 break;
511 case SORT_SONG_LENGTH:
512 SongUtil::SortSongPointerArrayBySongLength( arraySongs );
513 break;
514 case SORT_EASY_METER:
515 SongUtil::SortSongPointerArrayByMeter( arraySongs, DIFFICULTY_EASY );
516 break;
517 case SORT_MEDIUM_METER:
518 SongUtil::SortSongPointerArrayByMeter( arraySongs, DIFFICULTY_MEDIUM );
519 break;
520 case SORT_HARD_METER:
521 SongUtil::SortSongPointerArrayByMeter( arraySongs, DIFFICULTY_HARD );
522 break;
523 case SORT_CHALLENGE_METER:
524 SongUtil::SortSongPointerArrayByMeter( arraySongs, DIFFICULTY_CHALLENGE );
525 break;
526 default:
527 ASSERT(0); // unhandled SortOrder
528 }
529
530
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() );
536
537 switch( PREFSMAN->m_MusicWheelUsesSections )
538 {
539 case PrefsManager::NEVER:
540 bUseSections = false;
541 break;
542 case PrefsManager::ABC_ONLY:
543 if( so != SORT_TITLE && so != SORT_GROUP )
544 bUseSections = false;
545 break;
546 }
547
548 if( bUseSections )
549 {
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
557 // * sort. */
558 if( so != SORT_TOP_GRADES && so != SORT_TOP_GRADES_GROUPED && so != SORT_BPM )
559 SongUtil::SortSongPointerArrayBySectionName(arraySongs, so);
560
561 // make WheelItemDatas with sections
562 CString sLastSection = "";
563 int iSectionColorIndex = 0;
564 for( unsigned i=0; i< arraySongs.size(); i++ )
565 {
566 Song* pSong = arraySongs[i];
567 CString sThisSection = SongUtil::GetSectionNameFromSongAndSort( pSong, so );
568
569 if( sThisSection != sLastSection) // new section, make a section item
570 {
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;
575 }
576
577 arrayWheelItemDatas.push_back( WheelItemData( TYPE_SONG, pSong, sThisSection, NULL, SONGMAN->GetSongColor(pSong)) );
578 }
579 }
580 else
581 {
582 for( unsigned i=0; i<arraySongs.size(); i++ )
583 {
584 Song* pSong = arraySongs[i];
585 arrayWheelItemDatas.push_back( WheelItemData(TYPE_SONG, pSong, "", NULL, SONGMAN->GetSongColor(pSong)) );
586 }
587 }
588
589 if( so != SORT_ROULETTE )
590 {
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
598 {
599
600 if( SHOW_RANDOM && bFoundAnySong )
601 arrayWheelItemDatas.push_back( WheelItemData(TYPE_RANDOM, NULL, "", NULL, RageColor(1,0,0,1)) );
602 if( SHOW_ROULETTE )
603 arrayWheelItemDatas.push_back( WheelItemData(TYPE_ROULETTE, NULL, "", NULL, RageColor(1,0,0,1)) );
604 }
605 else // Roulette first, normal SM behavior
606 {
607 if( SHOW_ROULETTE )
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)) );
611 }
612 if( SHOW_PORTAL && bFoundAnySong )
613 arrayWheelItemDatas.push_back( WheelItemData(TYPE_PORTAL, NULL, "", NULL, RageColor(1,0,0,1)) );
614 }
615
616 if( GAMESTATE->IsExtraStage() || GAMESTATE->IsExtraStage2() )
617 {
618 Song* pSong;
619 Steps* pSteps;
620 PlayerOptions po;
621 SongOptions so;
622 SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(), pSong, pSteps, po, so );
623
624 for( unsigned i=0; i<arrayWheelItemDatas.size(); i++ )
625 {
626 if( arrayWheelItemDatas[i].m_pSong == pSong )
627 {
628 /* Change the song color. */
629 arrayWheelItemDatas[i].m_color = SONG_REAL_EXTRA_COLOR;
630 break;
631 }
632 }
633 }
634 break;
635 }
636 case SORT_ALL_COURSES:
637 case SORT_NONSTOP_COURSES:
638 case SORT_ONI_COURSES:
639 case SORT_ENDLESS_COURSES:
640 {
641 vector<Course*> apCourses;
642 switch( so )
643 {
644 case SORT_NONSTOP_COURSES:
645 SONGMAN->GetCourses( COURSE_TYPE_NONSTOP, apCourses, PREFSMAN->m_bAutogenGroupCourses );
646 break;
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 );
650 break;
651 case SORT_ENDLESS_COURSES:
652 SONGMAN->GetCourses( COURSE_TYPE_ENDLESS, apCourses, PREFSMAN->m_bAutogenGroupCourses );
653 break;
654 case SORT_ALL_COURSES:
655 SONGMAN->GetAllCourses( apCourses, PREFSMAN->m_bAutogenGroupCourses );
656 break;
657 default: ASSERT(0); break;
658 }
659
660 if( PREFSMAN->m_CourseSortOrder == PrefsManager::COURSE_SORT_SONGS )
661 {
662 CourseUtil::SortCoursePointerArrayByDifficulty( apCourses );
663 }
664 else
665 {
666 switch( PREFSMAN->m_CourseSortOrder )
667 {
668 case PrefsManager::COURSE_SORT_METER:
669 CourseUtil::SortCoursePointerArrayByAvgDifficulty( apCourses );
670 break;
671 case PrefsManager::COURSE_SORT_METER_SUM:
672 CourseUtil::SortCoursePointerArrayByTotalDifficulty( apCourses );
673 break;
674 case PrefsManager::COURSE_SORT_RANK:
675 CourseUtil::SortCoursePointerArrayByRanking( apCourses );
676 break;
677 default: ASSERT(0);
678 }
679
680 // since we can't agree, make it an option
681 if( PREFSMAN->m_bMoveRandomToEnd )
682 CourseUtil::MoveRandomToEnd( apCourses );
683 }
684
685 if( so == SORT_ALL_COURSES )
686 CourseUtil::SortCoursePointerArrayByType( apCourses );
687
688 arrayWheelItemDatas.clear(); // clear out the previous wheel items
689
690 CString sLastSection = "";
691 int iSectionColorIndex = 0;
692 for( unsigned i=0; i<apCourses.size(); i++ ) // foreach course
693 {
694 Course* pCourse = apCourses[i];
695
696 // if unlocks are on, make sure it is unlocked
697 if ( UNLOCKMAN->CourseIsLocked(pCourse) )
698 continue;
699
700 CString sThisSection = "";
701 if( so == SORT_ALL_COURSES )
702 {
703 switch( pCourse->GetPlayMode() )
704 {
705 case PLAY_MODE_ONI: sThisSection = "Oni"; break;
706 case PLAY_MODE_NONSTOP: sThisSection = "Nonstop"; break;
707 case PLAY_MODE_ENDLESS: sThisSection = "Endless"; break;
708 }
709 }
710
711 // check that this course has at least one song playable in the current style
712 if( !pCourse->IsPlayableIn(GAMESTATE->GetCurrentStyle()->m_StepsType) )
713 continue;
714
715 if( sThisSection != sLastSection ) // new section, make a section item
716 {
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;
721 }
722
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) );
725 }
726 break;
727 }
728 }
729
730 // init music status icons
731 for( unsigned i=0; i<arrayWheelItemDatas.size(); i++ )
732 {
733 Song* pSong = arrayWheelItemDatas[i].m_pSong;
734 if( pSong == NULL )
735 continue;
736
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 );
742 }
743
744 // init crowns
745 if( so == SORT_POPULARITY )
746 {
747 // init crown icons
748 for( unsigned i=0; i< min(3u,arrayWheelItemDatas.size()); i++ )
749 {
750 WheelItemData& WID = arrayWheelItemDatas[i];
751 WID.m_Flags.iPlayersBestNumber = i+1;
752 }
753 }
754
755 if( arrayWheelItemDatas.empty() )
756 {
757 arrayWheelItemDatas.push_back( WheelItemData(TYPE_SECTION, NULL, "- EMPTY -", NULL, RageColor(1,0,0,1)) );
758 }
759 }
760
761 void MusicWheel::RebuildAllMusicWheelItems()
762 {
763 RebuildMusicWheelItems( INT_MAX );
764 }
765
766 void MusicWheel::RebuildMusicWheelItems( int dist )
767 {
768 // rewind to first index that will be displayed;
769 int iFirstVisibleIndex = m_iSelection;
770 if( m_iSelection > int(m_CurWheelItemData.size()-1) )
771 m_iSelection = 0;
772
773 // find the first wheel item shown
774 iFirstVisibleIndex -= NUM_WHEEL_ITEMS/2;
775
776 ASSERT(m_CurWheelItemData.size());
777 wrap( iFirstVisibleIndex, m_CurWheelItemData.size() );
778
779 // iIndex is now the index of the lowest WheelItem to draw
780
781 if( dist == INT_MAX )
782 {
783 // Refresh all
784 for( int i=0; i<NUM_WHEEL_ITEMS; i++ )
785 {
786 int iIndex = iFirstVisibleIndex + i;
787 wrap( iIndex, m_CurWheelItemData.size() );
788
789 WheelItemData *data = m_CurWheelItemData[iIndex];
790 MusicWheelItem *display = m_MusicWheelItems[i];
791
792 bool bExpanded = (data->m_Type == TYPE_SECTION) ? (data->m_sText == m_sExpandedSectionName) : false;
793 display->LoadFromWheelItemData( data, bExpanded );
794 }
795 }
796 else
797 {
798 // Shift items and refresh only those that have changed.
799 CircularShift( m_MusicWheelItems, dist );
800 if( dist > 0 )
801 {
802 for( int i=NUM_WHEEL_ITEMS-dist; i<NUM_WHEEL_ITEMS; i++ )
803 {
804 int iIndex = iFirstVisibleIndex + i;
805 wrap( iIndex, m_CurWheelItemData.size() );
806
807 WheelItemData *data = m_CurWheelItemData[iIndex];
808 MusicWheelItem *display = m_MusicWheelItems[i];
809
810 bool bExpanded = (data->m_Type == TYPE_SECTION) ? (data->m_sText == m_sExpandedSectionName) : false;
811 display->LoadFromWheelItemData( data, bExpanded );
812 }
813 }
814 else if( dist < 0 )
815 {
816 for( int i=0; i<-dist; i++ )
817 {
818 int iIndex = iFirstVisibleIndex + i;
819 wrap( iIndex, m_CurWheelItemData.size() );
820
821 WheelItemData *data = m_CurWheelItemData[iIndex];
822 MusicWheelItem *display = m_MusicWheelItems[i];
823
824 bool bExpanded = (data->m_Type == TYPE_SECTION) ? (data->m_sText == m_sExpandedSectionName) : false;
825 display->LoadFromWheelItemData( data, bExpanded );
826 }
827 }
828 }
829 }
830
831 void MusicWheel::NotesOrTrailChanged( PlayerNumber pn ) // update grade graphics and top score
832 {
833 for( int i=0; i<NUM_WHEEL_ITEMS; i++ )
834 {
835 MusicWheelItem *display = m_MusicWheelItems[i];
836 display->RefreshGrades();
837 }
838 }
839
840 void MusicWheel::DrawItem( int i )
841 {
842 MusicWheelItem *display = m_MusicWheelItems[i];
843
844 const float fThisBannerPositionOffsetFromSelection = i - NUM_WHEEL_ITEMS/2 + m_fPositionOffsetFromSelection;
845 if( fabsf(fThisBannerPositionOffsetFromSelection) > NUM_WHEEL_ITEMS_TO_DRAW/2 )
846 return;
847
848 switch( m_WheelState )
849 {
850 case STATE_SELECTING_MUSIC:
851 case STATE_ROULETTE_SPINNING:
852 case STATE_ROULETTE_SLOWING_DOWN:
853 case STATE_RANDOM_SPINNING:
854 {
855 SetItemPosition( *display, fThisBannerPositionOffsetFromSelection );
856 }
857 break;
858 }
859
860 WheelBase::DrawItem(i, display, fThisBannerPositionOffsetFromSelection);
861 }
862
863 bool MusicWheel::IsSettled() const
864 {
865 if( m_Moving )
866 return false;
867 if( m_WheelState != STATE_SELECTING_MUSIC && m_WheelState != STATE_LOCKED )
868 return false;
869 if( m_fPositionOffsetFromSelection != 0 )
870 return false;
871
872 return true;
873 }
874
875 void MusicWheel::UpdateItems(float fDeltaTime )
876 {
877 for( unsigned i=0; i<unsigned(NUM_WHEEL_ITEMS); i++ )
878 {
879 MusicWheelItem *display = m_MusicWheelItems[i];
880
881 display->Update( fDeltaTime );
882 }
883 }
884
885 void MusicWheel::UpdateSwitch()
886 {
887 switch( m_WheelState )
888 {
889 case STATE_FLYING_OFF_BEFORE_NEXT_SORT:
890 {
891 Song* pPrevSelectedSong = m_CurWheelItemData[m_iSelection]->m_pSong;
892
893 SCREENMAN->PostMessageToTopScreen( SM_SortOrderChanged, 0 );
894
895 SetOpenGroup(SongUtil::GetSectionNameFromSongAndSort( pPrevSelectedSong, GAMESTATE->m_SortOrder ));
896
897 m_iSelection = 0;
898
899 //
900 // Select the previously selected item
901 //
902 switch( GAMESTATE->m_SortOrder )
903 {
904 default:
905 // Look for the last selected song or course
906 SelectSongOrCourse();
907 break;
908 case SORT_MODE_MENU:
909 SelectModeMenuItem();
910 break;
911 }
912
913 //
914 // Change difficulty for sorts by meter - XXX: do this with GameCommand?
915 //
916 Difficulty dc = DIFFICULTY_INVALID;
917 switch( GAMESTATE->m_SortOrder )
918 {
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;
923 }
924 if( dc != DIFFICULTY_INVALID )
925 {
926 FOREACH_PlayerNumber( p )
927 if( GAMESTATE->IsPlayerEnabled(p) )
928 GAMESTATE->m_PreferredDifficulty[p].Set( dc );
929 }
930
931 SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 );
932 RebuildAllMusicWheelItems();
933 TweenOnScreen(true);
934 m_WheelState = STATE_FLYING_ON_AFTER_NEXT_SORT;
935
936 SCREENMAN->ZeroNextUpdate();
937 }
938 break;
939
940 case STATE_FLYING_ON_AFTER_NEXT_SORT:
941 m_WheelState = STATE_SELECTING_MUSIC; // now, wait for input
942 break;
943
944 case STATE_TWEENING_ON_SCREEN:
945 m_fTimeLeftInState = 0;
946 // if( (GAMESTATE->IsExtraStage() && !PREFSMAN->m_bPickExtraStage) || GAMESTATE->IsExtraStage2() )
947 if( GAMESTATE->IsExtraStage2() )
948 {
949 m_WheelState = STATE_LOCKED;
950 SCREENMAN->PlayStartSound();
951 m_fLockedWheelVelocity = 0;
952 }
953 else
954 {
955 m_WheelState = STATE_SELECTING_MUSIC;
956 }
957 break;
958 case STATE_TWEENING_OFF_SCREEN:
959 m_WheelState = STATE_WAITING_OFF_SCREEN;
960 m_fTimeLeftInState = 0;
961 break;
962 case STATE_SELECTING_MUSIC:
963 m_fTimeLeftInState = 0;
964 break;
965 case STATE_ROULETTE_SPINNING:
966 case STATE_RANDOM_SPINNING:
967 break;
968 case STATE_WAITING_OFF_SCREEN:
969 break;
970 case STATE_LOCKED:
971 break;
972 case STATE_ROULETTE_SLOWING_DOWN:
973 if( m_iSwitchesLeftInSpinDown == 0 )
974 {
975 m_WheelState = STATE_LOCKED;
976 m_fTimeLeftInState = 0;
977 SCREENMAN->PlayStartSound();
978 m_fLockedWheelVelocity = 0;
979
980 /* Send this again so the screen starts sample music. */
981 SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 );
982 }
983 else
984 {
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];
989
990 LOG->Trace( "m_iSwitchesLeftInSpinDown id %d, m_fTimeLeftInState is %f", m_iSwitchesLeftInSpinDown, m_fTimeLeftInState );
991
992 if( m_iSwitchesLeftInSpinDown < 2 )
993 ChangeMusic(randomf(0,1) >= 0.5f? 1:-1);
994 else
995 ChangeMusic(1);
996 }
997 break;
998 default:
999 ASSERT(0); // all state changes should be handled explicitly
1000 break;
1001 }
1002 }
1003
1004 void MusicWheel::ChangeMusic(int dist)
1005 {
1006 m_iSelection += dist;
1007 wrap( m_iSelection, m_CurWheelItemData.size() );
1008
1009 RebuildMusicWheelItems( dist );
1010
1011 m_fPositionOffsetFromSelection += dist;
1012
1013 SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 );
1014
1015 /* If we're moving automatically, don't play this; it'll be called in Update.*/
1016 if(!IsMoving())
1017 m_soundChangeMusic.Play();
1018 }
1019
1020
1021 bool MusicWheel::ChangeSort( SortOrder new_so ) // return true if change successful
1022 {
1023 ASSERT( new_so < NUM_SORT_ORDERS );
1024 if( GAMESTATE->m_SortOrder == new_so )
1025 return false;
1026
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 )
1029 return false;
1030
1031 switch( m_WheelState )
1032 {
1033 case STATE_SELECTING_MUSIC:
1034 case STATE_FLYING_ON_AFTER_NEXT_SORT:
1035 break; // fall through
1036 default:
1037 return false; // don't continue
1038 }
1039
1040 SCREENMAN->PostMessageToTopScreen( SM_SortOrderChanging, 0 );
1041
1042 m_soundChangeSort.Play();
1043
1044 TweenOffScreen(true);
1045
1046 /* Save the new preference. */
1047 if( IsSongSort(new_so) )
1048 GAMESTATE->m_PreferredSortOrder = new_so;
1049 GAMESTATE->m_SortOrder = new_so;
1050
1051 GAMESTATE->m_SortOrder = new_so;
1052
1053 m_WheelState = STATE_FLYING_OFF_BEFORE_NEXT_SORT;
1054 return true;
1055 }
1056
1057 bool MusicWheel::NextSort() // return true if change successful
1058 {
1059 // don't allow NextSort when on the sort menu or mode menu
1060 if( GAMESTATE->m_SortOrder == SORT_MODE_MENU )
1061 return false;
1062
1063 // find the index of the current sort
1064 int cur = 0;
1065 while( cur < int(SONG_SORT_ORDERS.size()) && SONG_SORT_ORDERS[cur] != GAMESTATE->m_SortOrder )
1066 ++cur;
1067
1068 // move to the next sort with wrapping
1069 ++cur;
1070 wrap( cur, SONG_SORT_ORDERS.size() );
1071
1072 // apply new sort
1073 SortOrder soNew = SONG_SORT_ORDERS[cur];
1074 return ChangeSort( soNew );
1075 }
1076
1077 bool MusicWheel::Select() // return true if this selection ends the screen
1078 {
1079 LOG->Trace( "MusicWheel::Select()" );
1080
1081 switch( m_WheelState )
1082 {
1083 case STATE_FLYING_OFF_BEFORE_NEXT_SORT:
1084 case STATE_ROULETTE_SLOWING_DOWN:
1085 return false;
1086 }
1087
1088 m_Moving = 0;
1089
1090 if( m_WheelState == STATE_ROULETTE_SPINNING )
1091 {
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;
1095 return false;
1096 }
1097
1098
1099 if( m_WheelState == STATE_RANDOM_SPINNING )
1100 {
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 );
1106 return false;
1107 }
1108
1109 switch( m_CurWheelItemData[m_iSelection]->m_Type )
1110 {
1111 case TYPE_SECTION:
1112 {
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
1118
1119 m_soundExpand.Play();
1120
1121 SetOpenGroup(m_sExpandedSectionName);
1122 }
1123 return false;
1124 case TYPE_ROULETTE:
1125 StartRoulette();
1126 return false;
1127 case TYPE_RANDOM:
1128 StartRandom();
1129 return false;
1130 case TYPE_SONG:
1131 case TYPE_PORTAL:
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 );
1136 return true;
1137 case TYPE_COURSE:
1138 return true;
1139 case TYPE_SORT:
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;
1146 return false;
1147 default:
1148 ASSERT(0);
1149 return false;
1150 }
1151 }
1152
1153 void MusicWheel::StartRoulette()
1154 {
1155 MESSAGEMAN->Broadcast("StartRoulette");
1156 m_WheelState = STATE_ROULETTE_SPINNING;
1157 m_Moving = 1;
1158 m_TimeBeforeMovingBegins = 0;
1159 m_SpinSpeed = 1.0f/ROULETTE_SWITCH_SECONDS;
1160 SetOpenGroup("", SORT_ROULETTE);
1161 }
1162
1163 void MusicWheel::StartRandom()
1164 {
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 )
1170 {
1171 /* Shuffle and use the roulette wheel. */
1172 RandomGen rnd;
1173 random_shuffle( m_WheelItemDatas[SORT_ROULETTE].begin(), m_WheelItemDatas[SORT_ROULETTE].end(), rnd );
1174 SetOpenGroup( "", SORT_ROULETTE );
1175 }
1176 else
1177 {
1178 SetOpenGroup( "", GAMESTATE->m_PreferredSortOrder );
1179 }
1180
1181 m_Moving = -1;
1182 m_TimeBeforeMovingBegins = 0;
1183 m_SpinSpeed = 1.0f/ROULETTE_SWITCH_SECONDS;
1184 m_SpinSpeed *= 20.0f; /* faster! */
1185 m_WheelState = STATE_RANDOM_SPINNING;
1186
1187 SelectSong( GetPreferredSelectionForRandomOrPortal() );
1188
1189 this->Select();
1190 RebuildAllMusicWheelItems();
1191 }
1192
1193 void MusicWheel::SetOpenGroup(CString group, SortOrder so)
1194 {
1195 if( so != SORT_INVALID )
1196 GAMESTATE->m_SortOrder = so;
1197
1198 m_sExpandedSectionName = group;
1199
1200 WheelItemData *old = NULL;
1201 if(!m_CurWheelItemData.empty())
1202 old = m_CurWheelItemData[m_iSelection];
1203
1204 m_CurWheelItemData.clear();
1205 vector<WheelItemData> &from = m_WheelItemDatas[GAMESTATE->m_SortOrder];
1206 for( unsigned i = 0; i < from.size(); ++i )
1207 {
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 )
1212 continue;
1213
1214 /* Only show tutorial songs in arcade */
1215 if( GAMESTATE->m_PlayMode!=PLAY_MODE_REGULAR &&
1216 d.m_pSong &&
1217 d.m_pSong->IsTutorial() )
1218 continue;
1219
1220 m_CurWheelItemData.push_back(&d);
1221 }
1222
1223
1224 //
1225 // Try to select the item that was selected before changing groups
1226 //
1227 m_iSelection = 0;
1228
1229 for( unsigned i=0; i<m_CurWheelItemData.size(); i++ )
1230 {
1231 if( m_CurWheelItemData[i] == old )
1232 {
1233 m_iSelection=i;
1234 break;
1235 }
1236 }
1237
1238 RebuildAllMusicWheelItems();
1239 }
1240
1241 bool MusicWheel::IsRouletting() const
1242 {
1243 return m_WheelState == STATE_ROULETTE_SPINNING || m_WheelState == STATE_ROULETTE_SLOWING_DOWN ||
1244 m_WheelState == STATE_RANDOM_SPINNING;
1245 }
1246
1247 void MusicWheel::TweenOnScreenUpdateItems(bool changing_sort) {
1248 for( int i=0; i<NUM_WHEEL_ITEMS; i++ )
1249 {
1250 MusicWheelItem *display = m_MusicWheelItems[i];
1251 float fThisBannerPositionOffsetFromSelection = i - NUM_WHEEL_ITEMS/2 + m_fPositionOffsetFromSelection;
1252 SetItemPosition( *display, fThisBannerPositionOffsetFromSelection );
1253
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");
1258 if( changing_sort )
1259 display->HurryTweening( 0.25f );
1260 }
1261 }
1262
1263 void MusicWheel::TweenOffScreenUpdateItems(bool changing_sort) {
1264 for( int i=0; i<NUM_WHEEL_ITEMS; i++ )
1265 {
1266 MusicWheelItem *display = m_MusicWheelItems[i];
1267 float fThisBannerPositionOffsetFromSelection = i - NUM_WHEEL_ITEMS/2 + m_fPositionOffsetFromSelection;
1268 SetItemPosition( *display, fThisBannerPositionOffsetFromSelection );
1269
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");
1274 if( changing_sort )
1275 display->HurryTweening( 0.25f );
1276 }
1277 }
1278
1279 bool MusicWheel::MoveSpecific(int n)
1280 {
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 )
1287 {
1288 case STATE_SELECTING_MUSIC:
1289 break;
1290 case STATE_FLYING_OFF_BEFORE_NEXT_SORT:
1291 case STATE_FLYING_ON_AFTER_NEXT_SORT:
1292 if( n!= 0 )
1293 return false;
1294 break;
1295 default:
1296 return false; // don't continue
1297 }
1298
1299 if(m_Moving != 0 && n == 0 && m_TimeBeforeMovingBegins == 0)
1300 {
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
1303 * smoothly. */
1304 if(fabsf(m_fPositionOffsetFromSelection) < 0.25f )
1305 ChangeMusic(m_Moving);
1306
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 );
1310 }
1311 return true;
1312 }
1313
1314 Song* MusicWheel::GetSelectedSong()
1315 {
1316 switch( m_CurWheelItemData[m_iSelection]->m_Type )
1317 {
1318 case TYPE_PORTAL:
1319 return GetPreferredSelectionForRandomOrPortal();
1320 }
1321
1322 return m_CurWheelItemData[m_iSelection]->m_pSong;
1323 }
1324
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.
1327 *
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()
1332 {
1333 // probe to find a song that has the preferred
1334 // difficulties of each player
1335 vector<Difficulty> vDifficultiesToRequire;
1336 FOREACH_HumanPlayer(p)
1337 {
1338 if( GAMESTATE->m_PreferredDifficulty[p] == DIFFICULTY_INVALID )
1339 continue; // skip
1340
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 )
1346 continue; // skip
1347
1348 vDifficultiesToRequire.push_back( GAMESTATE->m_PreferredDifficulty[p] );
1349 }
1350
1351 CString sPreferredGroup = m_sExpandedSectionName;
1352 vector<WheelItemData> &wid = m_WheelItemDatas[GAMESTATE->m_SortOrder];
1353
1354 StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
1355
1356 #define NUM_PROBES 1000
1357 for( int i=0; i<NUM_PROBES; i++ )
1358 {
1359 /* Maintaining difficulties is higher priority than maintaining the current
1360 * group. */
1361 if( i == NUM_PROBES/4 )
1362 sPreferredGroup = "";
1363 if( i == NUM_PROBES/2 )
1364 vDifficultiesToRequire.clear();
1365
1366 int iSelection = rand() % wid.size();
1367 if( wid[iSelection].m_Type != TYPE_SONG )
1368 continue;
1369
1370 Song* pSong = wid[iSelection].m_pSong;
1371
1372 // ignore these
1373 if( pSong->IsCustomSong() )
1374 continue;
1375
1376 if( !sPreferredGroup.empty() && wid[iSelection].m_sText != sPreferredGroup )
1377 continue;
1378
1379 // There's an off possibility that somebody might have only one song with only beginner steps.
1380 if( i < 900 && pSong->IsTutorial() )
1381 continue;
1382
1383 FOREACH( Difficulty, vDifficultiesToRequire, d )
1384 if( !pSong->HasStepsTypeAndDifficulty(st,*d) )
1385 goto try_next;
1386 return wid[iSelection].m_pSong;
1387 try_next:
1388 ;
1389 }
1390 LOG->Warn( "Couldn't find any songs" );
1391 return wid[0].m_pSong;
1392 }
1393
1394 void MusicWheel::FinishChangingSorts()
1395 {
1396 FinishTweening();
1397 m_WheelState = STATE_SELECTING_MUSIC;
1398 m_fTimeLeftInState = 0;
1399 }
1400
1401 /*
1402 * (c) 2001-2004 Chris Danford, Chris Gomez, Glenn Maynard
1403 * All rights reserved.
1404 *
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.
1414 *
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.
1424 */