Files
itgmania212121/src/MusicWheel.cpp
T

1928 lines
61 KiB
C++
Raw Normal View History

2012-07-08 10:58:53 -05:00
#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 "ProfileManager.h"
2012-07-08 10:58:53 -05:00
#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 "Style.h"
#include "PlayerState.h"
#include "CommonMetrics.h"
#include "MessageManager.h"
#include "LocalizedString.h"
2023-04-19 14:22:59 +02:00
#include <cmath>
2023-04-19 23:04:25 +02:00
#include <cstddef>
2023-04-20 19:02:13 +02:00
#include <vector>
2023-04-19 14:22:59 +02:00
2012-07-08 10:58:53 -05:00
static Preference<bool> g_bMoveRandomToEnd( "MoveRandomToEnd", false );
static Preference<bool> g_bPrecacheAllSorts( "PreCacheAllWheelSorts", false);
2023-04-19 14:22:59 +02:00
#define NUM_WHEEL_ITEMS ((int)std::ceil(NUM_WHEEL_ITEMS_TO_DRAW+2))
2012-07-08 10:58:53 -05:00
#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()) );
2024-10-01 00:43:07 -07:00
static RString SECTION_COLORS_NAME( size_t i ) { return ssprintf("SectionColor%d",int(i+1)); }
2012-07-08 10:58:53 -05:00
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;
break;
2012-07-08 10:58:53 -05:00
default:
break;
2012-07-08 10:58:53 -05:00
}
2023-04-19 14:22:59 +02:00
return so;
2012-07-08 10:58:53 -05:00
}
MusicWheelItem *MusicWheel::MakeItem()
{
return new MusicWheelItem;
}
2023-04-19 14:22:59 +02:00
void MusicWheel::Load( RString sType )
2012-07-08 10:58:53 -05:00
{
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");
HIDE_ACTIVE_SECTION_TITLE .Load(sType,"HideActiveSectionTitle");
2012-07-08 10:58:53 -05:00
REMIND_WHEEL_POSITIONS .Load(sType,"RemindWheelPositions");
std::vector<RString> vsModeChoiceNames;
2012-07-08 10:58:53 -05:00
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");
std::vector<RString> vsCustomItemNames;
2012-07-08 10:58:53 -05:00
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();
2023-04-19 14:22:59 +02:00
2012-07-08 10:58:53 -05:00
FOREACH_ENUM( SortOrder, so ) {
m_WheelItemDatasStatus[so]=INVALID;
}
}
void MusicWheel::BeginScreen()
{
RageTimer timer;
RString times;
2023-04-19 14:22:59 +02:00
FOREACH_ENUM( SortOrder, so ) {
2012-07-08 10:58:53 -05:00
if(m_WheelItemDatasStatus[so]!=INVALID) {
m_WheelItemDatasStatus[so]=NEEDREFILTER;
2023-04-19 14:22:59 +02:00
2012-07-08 10:58:53 -05:00
}
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
2023-04-19 14:22:59 +02:00
// after building wheel item data.)
2012-07-08 10:58:53 -05:00
{
const std::vector<MusicWheelItemData *> &from = getWheelItemsData(SORT_MODE_MENU);
2012-07-08 10:58:53 -05:00
for( unsigned i=0; i<from.size(); i++ )
{
ASSERT( !from[i]->m_pAction.isNull() );
2012-07-08 10:58:53 -05:00
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;
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())
{
std::vector<Song*> vTemp = SONGMAN->GetSongs(GAMESTATE->m_sPreferredSongGroup);
2012-07-08 10:58:53 -05:00
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();
/* Invalidate current Song if it can't be played
* because there are not enough stages remaining. */
2019-06-22 12:35:38 -07:00
if(GAMESTATE->m_pCurSong != nullptr &&
GameState::GetNumStagesMultiplierForSong(GAMESTATE->m_pCurSong) >
GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer())
{
2019-06-22 12:35:38 -07:00
GAMESTATE->m_pCurSong.Set(nullptr);
}
/* Invalidate current Steps if it can't be played
* because there are not enough stages remaining. */
FOREACH_ENUM(PlayerNumber, p)
{
2019-06-22 12:35:38 -07:00
if(GAMESTATE->m_pCurSteps[p] != nullptr)
{
std::vector<Steps*> vpPossibleSteps;
2019-06-22 12:35:38 -07:00
if(GAMESTATE->m_pCurSong != nullptr)
{
SongUtil::GetPlayableSteps(GAMESTATE->m_pCurSong, vpPossibleSteps);
}
bool bStepsIsPossible = find(vpPossibleSteps.begin(), vpPossibleSteps.end(), GAMESTATE->m_pCurSteps[p]) == vpPossibleSteps.end();
if(!bStepsIsPossible)
{
2019-06-22 12:35:38 -07:00
GAMESTATE->m_pCurSteps[p].Set(nullptr);
}
}
}
2012-07-08 10:58:53 -05:00
}
MusicWheel::~MusicWheel()
{
FOREACH_ENUM( SortOrder, so ) {
std::vector<MusicWheelItemData*>::iterator i = m__UnFilteredWheelItemDatas[so].begin();
std::vector<MusicWheelItemData*>::iterator iEnd = m__UnFilteredWheelItemDatas[so].end();
2012-07-08 10:58:53 -05:00
for( ; i != iEnd; ++i ) {
delete *i;
}
}
}
void MusicWheel::ReloadSongList()
{
int songIdxToPreserve = m_iSelection;
// Remove the song from any sorting caches:
FOREACH_ENUM( SortOrder, so ) {
m_WheelItemDatasStatus[so]=INVALID;
}
// rebuild the info associated with this sort order
readyWheelItemsData(GAMESTATE->m_SortOrder);
// re-open the section to refresh song counts, etc.
SetOpenSection(m_sExpandedSectionName);
// navigate to the song nearest to what was previously selected
m_iSelection = songIdxToPreserve;
RebuildWheelItems();
// refresh the song preview
SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 );
}
2012-07-08 10:58:53 -05:00
/* 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...
std::vector<MusicWheelItemData *> &wiWheelItems = getWheelItemsData(GAMESTATE->m_SortOrder);
2012-07-08 10:58:53 -05:00
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 )
{
2013-05-03 23:11:42 -04:00
if( p == nullptr )
2012-07-08 10:58:53 -05:00
return false;
unsigned i;
std::vector<MusicWheelItemData *> &from = getWheelItemsData(GAMESTATE->m_SortOrder);
if (GAMESTATE->sLastOpenSection != "" && (GAMESTATE->m_SortOrder == SORT_PREFERRED || GAMESTATE->m_SortOrder == SORT_METER)) {
// Return to the last open section if it is defined and exists in the current sort
for( i=0; i<from.size(); i++ )
2012-07-08 10:58:53 -05:00
{
if( from[i]->m_sText == GAMESTATE->sLastOpenSection )
{
// make its group the currently expanded group
SetOpenSection( from[i]->m_sText );
break;
}
}
} else {
for( i=0; i<from.size(); i++ )
{
if( from[i]->m_pSong == p )
{
// make its group the currently expanded group
SetOpenSection( from[i]->m_sText );
break;
}
2012-07-08 10:58:53 -05:00
}
}
if( i == from.size() )
return false;
for( i=0; i<m_CurWheelItemData.size(); i++ )
{
if( GetCurWheelItemData(i)->m_pSong == p )
m_iSelection = i; // select it
}
return true;
}
bool MusicWheel::SelectCourse( const Course *p )
{
2013-05-03 23:11:42 -04:00
if( p == nullptr )
2012-07-08 10:58:53 -05:00
return false;
unsigned i;
std::vector<MusicWheelItemData *> &from = getWheelItemsData(GAMESTATE->m_SortOrder);
2012-07-08 10:58:53 -05:00
for( i=0; i<from.size(); i++ )
{
if( from[i]->m_pCourse == p )
{
// make its group the currently expanded group
SetOpenSection( from[i]->m_sText );
break;
}
}
if( i == from.size() )
return false;
for( i=0; i<m_CurWheelItemData.size(); i++ )
{
if( GetCurWheelItemData(i)->m_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 std::vector<MusicWheelItemData *> &from = getWheelItemsData(GAMESTATE->m_SortOrder);
2012-07-08 10:58:53 -05:00
unsigned i;
for( i=0; i<from.size(); i++ )
{
const GameCommand &gc = *from[i]->m_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; i<m_CurWheelItemData.size(); i++ )
{
if( GetCurWheelItemData(i)->m_pAction->m_sName != m_sLastModeMenuItem )
continue;
m_iSelection = i; // select it
break;
}
return true;
}
// bool MusicWheel::SelectCustomItem()
void MusicWheel::GetSongList( std::vector<Song*> &arraySongs, SortOrder so )
2012-07-08 10:58:53 -05:00
{
std::vector<Song*> apAllSongs;
2012-07-08 10:58:53 -05:00
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;
}
[[fallthrough]];
2012-07-08 10:58:53 -05:00
default:
apAllSongs = SONGMAN->GetAllSongs();
break;
}
FOREACH_PlayerNumber(pn)
{
if(GAMESTATE->IsPlayerEnabled(pn))
{
Profile* prof= PROFILEMAN->GetProfile(pn);
2024-10-01 00:43:07 -07:00
for(size_t i= 0; i < prof->m_songs.size(); ++i)
{
apAllSongs.push_back(prof->m_songs[i]);
}
}
}
2012-07-08 10:58:53 -05:00
// filter songs that we don't have enough stages to play
{
std::vector<Song*> vTempSongs;
2012-07-08 10:58:53 -05:00
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; i<apAllSongs.size(); i++ )
{
Song* pSong = apAllSongs[i];
int iLocked = UNLOCKMAN->SongIsLocked( 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(PLAYER_INVALID)->m_StepsType,GAMESTATE->m_PreferredDifficulty[GAMESTATE->GetFirstHumanPlayer()] ) )
2012-07-08 10:58:53 -05:00
arraySongs.push_back( pSong );
}
else if( CommonMetrics::AUTO_SET_STYLE || GAMESTATE->GetCurrentStyle(PLAYER_INVALID) == nullptr )
2012-07-08 10:58:53 -05:00
{
if (SongUtil::IsSongPlayable(pSong))
arraySongs.push_back( pSong );
} else
{
2022-04-09 13:11:17 +02:00
// If the song has at least one steps, add it.
if( pSong->HasStepsType(GAMESTATE->GetCurrentStyle(PLAYER_INVALID)->m_StepsType) )
arraySongs.push_back( pSong );
2012-07-08 10:58:53 -05:00
}
}
/* 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(PLAYER_INVALID), pSong, pSteps );
2012-07-08 10:58:53 -05:00
if( find( arraySongs.begin(), arraySongs.end(), pSong ) == arraySongs.end() )
arraySongs.push_back( pSong );
}
}
void MusicWheel::BuildWheelItemDatas( std::vector<MusicWheelItemData *> &arrayWheelItemDatas, SortOrder so )
2012-07-08 10:58:53 -05:00
{
switch( so )
{
case SORT_MODE_MENU:
{
2023-04-19 14:22:59 +02:00
arrayWheelItemDatas.clear(); // clear out the previous wheel items
std::vector<RString> vsNames;
2012-07-08 10:58:53 -05:00
split( MODE_MENU_CHOICE_NAMES, ",", vsNames );
for( unsigned i=0; i<vsNames.size(); ++i )
{
MusicWheelItemData wid( WheelItemDataType_Sort, nullptr, "", nullptr, nullptr, SORT_MENU_COLOR, 0 );
2012-07-08 10:58:53 -05:00
wid.m_pAction = HiddenPtr<GameCommand>( 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;
}
2023-12-12 17:56:44 -05:00
case SORT_METER:
2012-07-08 10:58:53 -05:00
case SORT_PREFERRED:
case SORT_ROULETTE:
case SORT_GROUP:
case SORT_TITLE:
case SORT_BPM:
case SORT_POPULARITY:
case SORT_POPULARITY_P1:
case SORT_POPULARITY_P2:
2012-07-08 10:58:53 -05:00
case SORT_TOP_GRADES:
case SORT_TOP_GRADES_P1:
case SORT_TOP_GRADES_P2:
2012-07-08 10:58:53 -05:00
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:
2013-10-24 00:12:49 -05:00
case SORT_DOUBLE_EASY_METER:
case SORT_DOUBLE_MEDIUM_METER:
case SORT_DOUBLE_HARD_METER:
case SORT_DOUBLE_CHALLENGE_METER:
2012-07-08 10:58:53 -05:00
case SORT_LENGTH:
case SORT_RECENT:
case SORT_RECENT_P1:
case SORT_RECENT_P2:
2012-07-08 10:58:53 -05:00
{
// Make an array of Song*, then sort them
std::vector<Song*> arraySongs;
2012-07-08 10:58:53 -05:00
GetSongList( arraySongs, so );
bool bUseSections = true;
// sort the songs
switch( so )
{
2023-12-12 17:56:44 -05:00
case SORT_METER:
SONGMAN->UpdateMeterSort(arraySongs);
break;
2012-07-08 10:58:53 -05:00
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() );
2025-02-26 02:54:58 -05:00
bUseSections = true;
2012-07-08 10:58:53 -05:00
break;
case SORT_POPULARITY_P1:
if( PROFILEMAN->IsPersistentProfile(PLAYER_1) )
SongUtil::SortSongPointerArrayByNumPlays( arraySongs, ProfileSlot_Player1, true );
if( (int) arraySongs.size() > MOST_PLAYED_SONGS_TO_SHOW )
arraySongs.erase( arraySongs.begin()+MOST_PLAYED_SONGS_TO_SHOW, arraySongs.end() );
bUseSections = true;
break;
case SORT_POPULARITY_P2:
if( PROFILEMAN->IsPersistentProfile(PLAYER_2) )
SongUtil::SortSongPointerArrayByNumPlays( arraySongs, ProfileSlot_Player2, true );
if( (int) arraySongs.size() > MOST_PLAYED_SONGS_TO_SHOW )
arraySongs.erase( arraySongs.begin()+MOST_PLAYED_SONGS_TO_SHOW, arraySongs.end() );
bUseSections = true;
break;
2012-07-08 10:58:53 -05:00
case SORT_TOP_GRADES:
SongUtil::SortSongPointerArrayByGrades( arraySongs, true );
break;
case SORT_TOP_GRADES_P1:
// Check if player profile is persistent
if( PROFILEMAN->IsPersistentProfile(PLAYER_1) )
SongUtil::SortSongPointerArrayByProfileGrades( arraySongs, true, PLAYER_1);
break;
case SORT_TOP_GRADES_P2:
if( PROFILEMAN->IsPersistentProfile(PLAYER_2) )
SongUtil::SortSongPointerArrayByProfileGrades( arraySongs, true, PLAYER_2);
2012-07-08 10:58:53 -05:00
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() );
2025-02-26 02:54:58 -05:00
bUseSections = true;
2012-07-08 10:58:53 -05:00
break;
case SORT_RECENT_P1:
if( PROFILEMAN->IsPersistentProfile(PLAYER_1) )
SongUtil::SortByMostRecentlyPlayedForProfile( arraySongs, PLAYER_1 );
if( (int) arraySongs.size() > RECENT_SONGS_TO_SHOW )
arraySongs.erase( arraySongs.begin()+RECENT_SONGS_TO_SHOW, arraySongs.end() );
bUseSections = true;
break;
case SORT_RECENT_P2:
if( PROFILEMAN->IsPersistentProfile(PLAYER_2) )
SongUtil::SortByMostRecentlyPlayedForProfile( arraySongs, PLAYER_2 );
if( (int) arraySongs.size() > RECENT_SONGS_TO_SHOW )
arraySongs.erase( arraySongs.begin()+RECENT_SONGS_TO_SHOW, arraySongs.end() );
bUseSections = true;
break;
2012-07-08 10:58:53 -05:00
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
2023-04-19 14:22:59 +02:00
arrayWheelItemDatas.clear(); // clear out the previous wheel items
2012-07-08 10:58:53 -05:00
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
2023-04-19 14:22:59 +02:00
* keep getting OTHER split up without this. However, it puts the
2012-07-08 10:58:53 -05:00
* 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_GROUP:
SongUtil::SortSongPointerArrayByGroup(arraySongs);
break;
2023-12-12 17:56:44 -05:00
case SORT_METER:
2012-07-08 10:58:53 -05:00
case SORT_PREFERRED:
case SORT_TOP_GRADES:
case SORT_TOP_GRADES_P1:
case SORT_TOP_GRADES_P2:
2012-07-08 10:58:53 -05:00
case SORT_BPM:
case SORT_LENGTH:
2012-07-08 10:58:53 -05:00
break; // don't sort by section
default:
SongUtil::SortSongPointerArrayBySectionName(arraySongs, so);
break;
}
}
// make WheelItemDatas with sections
RString sLastSection = "";
int iSectionColorIndex = 0;
2023-12-12 17:56:44 -05:00
switch (so) {
case SORT_PREFERRED:
// If the sort order is Preferred handle it differently because we already know the sections
if( bUseSections )
2012-07-08 10:58:53 -05:00
{
2023-12-12 17:56:44 -05:00
// Get mappping of section names to songs
std::map<RString, std::vector<Song*>> preferredSortSongsMap = SONGMAN->GetPreferredSortSongsMap();
for (auto const& [sectionName, songs] : SONGMAN->GetPreferredSortSongsMap())
2012-07-08 10:58:53 -05:00
{
2023-12-12 17:56:44 -05:00
// todo: preferred sort section color handling? -aj
RageColor colorSection = SECTION_COLORS.GetValue(iSectionColorIndex);
iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS;
// Add the section item
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, nullptr, sectionName, nullptr, nullptr, colorSection, songs.size()) );
2023-12-12 17:56:44 -05:00
// Add all the songs in this section
for (auto const& song : songs)
{
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Song, song, sectionName, nullptr, nullptr, SONGMAN->GetSongColor(song), 0) );
2023-12-12 17:56:44 -05:00
}
2012-07-08 10:58:53 -05:00
}
}
2023-12-12 17:56:44 -05:00
break;
case SORT_METER:
if( bUseSections )
{
2023-12-12 17:56:44 -05:00
int iSectionCount = 0;
for (auto const& [sectionName, songs] : SONGMAN->GetMeterToSongsMap()) {
RageColor colorSection = SECTION_COLORS.GetValue(iSectionColorIndex);
iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS;
// Add the section item
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, nullptr, ssprintf("%d",sectionName), nullptr, nullptr, colorSection, songs.size()) );
2023-12-12 17:56:44 -05:00
// Add all the songs in this section
for (auto const& song : songs)
{
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Song, song, ssprintf("%d",sectionName), nullptr, nullptr, SONGMAN->GetSongColor(song), 0) );
}
2023-12-12 17:56:44 -05:00
}
}
break;
case SORT_GROUP:
for( unsigned i=0; i< arraySongs.size(); i++ )
{
Song* pSong = arraySongs[i];
Group* pGroup = SONGMAN->GetGroup(pSong);
if( bUseSections )
{
RString sThisSection = pGroup->GetGroupName();
2024-10-04 10:10:11 -04:00
if( sThisSection != sLastSection )
{
int iSectionCount = 0;
// Count songs in this section
unsigned j;
for( j=i; j < arraySongs.size(); j++ )
{
if ( SONGMAN->GetGroup(arraySongs[j]) == nullptr ) {
LOG->Warn( "Song %s has no group!", arraySongs[j]->GetSongDir().c_str() );
continue;
} else if ( SONGMAN->GetGroup(arraySongs[j])->GetGroupName() != 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(sThisSection) : SECTION_COLORS.GetValue(iSectionColorIndex);
iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS;
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, nullptr, sThisSection, nullptr, pGroup, colorSection, iSectionCount) );
sLastSection = sThisSection;
}
}
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Song, pSong, sLastSection, nullptr, pGroup, SONGMAN->GetSongColor(pSong), 0) );
}
break;
2023-12-12 17:56:44 -05:00
default:
for( unsigned i=0; i< arraySongs.size(); i++ )
{
Song* pSong = arraySongs[i];
if( bUseSections )
{
RString sThisSection = SongUtil::GetSectionNameFromSongAndSort( pSong, so );
2023-12-12 17:56:44 -05:00
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(sThisSection) : SECTION_COLORS.GetValue(iSectionColorIndex);
2023-12-12 17:56:44 -05:00
iSectionColorIndex = (iSectionColorIndex+1) % NUM_SECTION_COLORS;
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Section, nullptr, sThisSection, nullptr, nullptr, colorSection, iSectionCount) );
2023-12-12 17:56:44 -05:00
sLastSection = sThisSection;
}
}
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Song, pSong, sLastSection, nullptr, nullptr, SONGMAN->GetSongColor(pSong), 0) );
2012-07-08 10:58:53 -05:00
}
2023-12-12 17:56:44 -05:00
break;
2012-07-08 10:58:53 -05:00
}
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, nullptr, "", nullptr, nullptr, ROULETTE_COLOR, 0) );
2012-07-08 10:58:53 -05:00
// 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, nullptr, "", nullptr, nullptr, RANDOM_COLOR, 0) );
2012-07-08 10:58:53 -05:00
if( SHOW_PORTAL && bFoundAnySong )
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Portal, nullptr, "", nullptr, nullptr, PORTAL_COLOR, 0) );
2012-07-08 10:58:53 -05:00
// add custom wheel items
std::vector<RString> vsNames;
2012-07-08 10:58:53 -05:00
split( CUSTOM_WHEEL_ITEM_NAMES, ",", vsNames );
for( unsigned i=0; i<vsNames.size(); ++i )
{
MusicWheelItemData wid( WheelItemDataType_Custom, nullptr, "", nullptr, nullptr, CUSTOM_CHOICE_COLORS.GetValue(vsNames[i]), 0 );
2012-07-08 10:58:53 -05:00
wid.m_pAction = HiddenPtr<GameCommand>( 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(PLAYER_INVALID), pSong, pSteps );
2023-04-19 14:22:59 +02:00
2012-07-08 10:58:53 -05:00
for( unsigned i=0; i<arrayWheelItemDatas.size(); i++ )
{
if( arrayWheelItemDatas[i]->m_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;
std::vector<CourseType> vct;
2012-07-08 10:58:53 -05:00
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;
2012-12-27 16:59:35 -05:00
default:
FAIL_M(ssprintf("Wrong sort order: %i", so));
2012-07-08 10:58:53 -05:00
}
std::vector<Course*> apCourses;
2013-04-28 18:03:24 -04:00
for (CourseType const &ct : vct)
2012-07-08 10:58:53 -05:00
{
if( bOnlyPreferred )
2013-04-28 18:03:24 -04:00
SONGMAN->GetPreferredSortCourses( ct, apCourses, PREFSMAN->m_bAutogenGroupCourses );
2012-07-08 10:58:53 -05:00
else
2013-04-28 18:03:24 -04:00
SONGMAN->GetCourses( ct, apCourses, PREFSMAN->m_bAutogenGroupCourses );
2012-07-08 10:58:53 -05:00
}
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 );
2023-04-19 14:22:59 +02:00
arrayWheelItemDatas.clear(); // clear out the previous wheel items
2012-07-08 10:58:53 -05:00
RString sLastSection = "";
int iSectionColorIndex = 0;
for( unsigned i=0; i<apCourses.size(); i++ ) // foreach course
{
Course* pCourse = apCourses[i];
// if unlocks are on, make sure it is unlocked
if ( UNLOCKMAN->CourseIsLocked(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(PLAYER_INVALID)->m_StepsType) )
2012-07-08 10:58:53 -05:00
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, nullptr, sThisSection, nullptr, nullptr, c, 0) );
2012-07-08 10:58:53 -05:00
sLastSection = sThisSection;
}
RageColor c = ( pCourse->m_sGroupName.size() == 0 ) ? pCourse->GetColor() : SONGMAN->GetCourseColor(pCourse);
arrayWheelItemDatas.push_back( new MusicWheelItemData(WheelItemDataType_Course, nullptr, sThisSection, pCourse, nullptr, c, 0) );
2012-07-08 10:58:53 -05:00
}
break;
}
default:
break;
}
// init music status icons
2013-04-27 13:56:25 -04:00
for (MusicWheelItemData *WID : arrayWheelItemDatas)
2012-07-08 10:58:53 -05:00
{
2013-05-03 23:01:54 -04:00
if( WID->m_pSong != nullptr )
2012-07-08 10:58:53 -05:00
{
2019-06-22 12:35:38 -07:00
WID->m_Flags.bHasBeginnerOr1Meter = WID->m_pSong->IsEasy( GAMESTATE->GetCurrentStyle(PLAYER_INVALID)->m_StepsType ) && SHOW_EASY_FLAG;
2013-04-27 13:56:25 -04:00
WID->m_Flags.bEdits = false;
std::set<StepsType> vStepsType;
2013-04-27 13:56:25 -04:00
SongUtil::GetPlayableStepsTypes( WID->m_pSong, vStepsType );
2013-04-27 13:54:28 -04:00
for (StepsType const &type : vStepsType)
2013-04-27 13:56:25 -04:00
WID->m_Flags.bEdits |= WID->m_pSong->HasEdits( type );
WID->m_Flags.iStagesForSong = GameState::GetNumStagesMultiplierForSong( WID->m_pSong );
2012-07-08 10:58:53 -05:00
}
2013-05-03 23:01:54 -04:00
else if( WID->m_pCourse != nullptr )
2012-07-08 10:58:53 -05:00
{
2013-04-27 13:56:25 -04:00
WID->m_Flags.bHasBeginnerOr1Meter = false;
WID->m_Flags.bEdits = WID->m_pCourse->IsAnEdit();
WID->m_Flags.iStagesForSong = 1;
2012-07-08 10:58:53 -05:00
}
}
}
std::vector<MusicWheelItemData *> & MusicWheel::getWheelItemsData(SortOrder so) {
2012-07-08 10:58:53 -05:00
// Update the popularity and init icons.
2023-04-19 14:22:59 +02:00
readyWheelItemsData(so);
2012-07-08 10:58:53 -05:00
return m__WheelItemDatas[so];
}
void MusicWheel::readyWheelItemsData(SortOrder so) {
if(m_WheelItemDatasStatus[so] == VALID && so != SORT_PREFERRED)
return;
2012-07-08 10:58:53 -05:00
std::vector<MusicWheelItemData*> &aUnFilteredDatas = m__UnFilteredWheelItemDatas[so];
2012-07-08 10:58:53 -05:00
if(m_WheelItemDatasStatus[so]==INVALID) {
BuildWheelItemDatas( aUnFilteredDatas, so );
}
FilterWheelItemDatas( aUnFilteredDatas, m__WheelItemDatas[so], so );
// The preferred sort's songs are subject to change during a session (particularly if two players have different preferred songs)
// thus it's status should remain invalid so the wheel items are rebuilt each time in case of change.
if (so != SORT_PREFERRED) {
m_WheelItemDatasStatus[so]=VALID;
2012-07-08 10:58:53 -05:00
}
}
void MusicWheel::FilterWheelItemDatas(std::vector<MusicWheelItemData *> &aUnFilteredDatas, std::vector<MusicWheelItemData *> &aFilteredData, SortOrder so )
2012-07-08 10:58:53 -05:00
{
aFilteredData.clear();
2012-07-08 10:58:53 -05:00
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;
}
}
std::vector<bool> aiRemove;
2012-07-08 10:58:53 -05:00
aiRemove.insert( aiRemove.begin(), unfilteredSize, false );
const int iMaxStagesForSong = GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer();
2013-05-03 23:39:52 -04:00
Song *pExtraStageSong = nullptr;
2012-07-08 10:58:53 -05:00
if( GAMESTATE->IsAnExtraStage() )
{
Steps *pSteps;
SONGMAN->GetExtraStageInfo( GAMESTATE->IsExtraStage2(), GAMESTATE->GetCurrentStyle(PLAYER_INVALID), pExtraStageSong, pSteps );
2012-07-08 10:58:53 -05:00
}
/* 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(PLAYER_INVALID)->m_StepsType) )
2012-07-08 10:58:53 -05:00
{
aiRemove[i] = true;
continue;
}
2023-04-19 14:22:59 +02:00
2012-07-08 10:58:53 -05:00
// 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(PLAYER_INVALID)->m_StepsType) )
2012-07-08 10:58:53 -05:00
aiRemove[i] = true;
}
}
/* Filter out the songs we're removing. */
2023-04-19 14:22:59 +02:00
2012-07-08 10:58:53 -05:00
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 || so == SORT_POPULARITY_P1 || so == SORT_POPULARITY_P2 )
2012-07-08 10:58:53 -05:00
{
for( unsigned i=0; i < std::min<unsigned int>(3u, aFilteredData.size()); i++ )
2012-07-08 10:58:53 -05:00
{
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, nullptr, EMPTY_STRING, nullptr, nullptr, EMPTY_COLOR, 0) );
2012-07-08 10:58:53 -05:00
}
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:
2012-12-27 16:59:35 -05:00
FAIL_M(ssprintf("Invalid wheel state: %i", m_WheelState));
2012-07-08 10:58:53 -05:00
}
}
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(true);
2012-07-08 10:58:53 -05:00
}
bool MusicWheel::ChangeSort( SortOrder new_so, bool allowSameSort ) // return true if change successful
{
ASSERT( new_so < NUM_SortOrder );
// Reset LastOpenSection as sections differ between sorts
GAMESTATE->sLastOpenSection = "";
// NOTE(crashcringle): Ignore allowSameSort if we're using SORT_PREFERRED.
// Each player has their own preferred songs which sorts the songs differently
if( GAMESTATE->m_SortOrder == new_so && (!allowSameSort && new_so != SORT_PREFERRED ))
{
2012-07-08 10:58:53 -05:00
return false;
}
2012-07-08 10:58:53 -05:00
// 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 )
{
2012-07-08 10:58:53 -05:00
return false;
}
2012-07-08 10:58:53 -05:00
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(true);
2012-07-08 10:58:53 -05:00
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;
std::vector<SortOrder> aSortOrders;
2012-07-08 10:58:53 -05:00
{
Lua *L = LUA->Get();
SORT_ORDERS.PushSelf( L );
FOREACH_LUATABLEI( L, -1, i )
{
SortOrder so = Enum::Check<SortOrder>( 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 = std::max(m_fPositionOffsetFromSelection, 0.3f);
2012-07-08 10:58:53 -05:00
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 )
{
2023-04-19 14:22:59 +02:00
case WheelItemDataType_Roulette:
2012-07-08 10:58:53 -05:00
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;
}
}
2023-04-19 14:22:59 +02:00
void MusicWheel::StartRoulette()
2012-07-08 10:58:53 -05:00
{
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;
std::shuffle( getWheelItemsData(SORT_ROULETTE).begin(), getWheelItemsData(SORT_ROULETTE).end(), rnd );
2012-07-08 10:58:53 -05:00
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() );
2013-05-03 23:39:52 -04:00
const WheelItemBaseData *old = nullptr;
2012-07-08 10:58:53 -05:00
if( !m_CurWheelItemData.empty() )
old = GetCurWheelItemData(m_iSelection);
std::vector<const Style*> vpPossibleStyles;
2012-07-08 10:58:53 -05:00
if( CommonMetrics::AUTO_SET_STYLE )
GAMEMAN->GetCompatibleStyles( GAMESTATE->m_pCurGame, GAMESTATE->GetNumPlayersEnabled(), vpPossibleStyles );
m_CurWheelItemData.clear();
std::vector<MusicWheelItemData *> &from = getWheelItemsData(GAMESTATE->m_SortOrder);
2012-07-08 10:58:53 -05:00
m_CurWheelItemData.reserve( from.size() );
for( unsigned i = 0; i < from.size(); ++i )
{
MusicWheelItemData &d = *from[i];
// Hide songs/courses which are not in the active section
2012-07-08 10:58:53 -05:00
if( (d.m_Type == WheelItemDataType_Song || d.m_Type == WheelItemDataType_Course) && !d.m_sText.empty() &&
d.m_sText != group )
continue;
// In certain situations (e.g. simulating Pump it Up or IIDX),
// themes may want to hide inactive section headings as well.
if( HIDE_INACTIVE_SECTIONS && d.m_Type == WheelItemDataType_Section && group != "" ) {
// Based on the HideActiveSectionTitle metric, we either
// hide all section titles, or only those which are not
// currently open.
if ( HIDE_ACTIVE_SECTION_TITLE || d.m_sText != group )
continue;
}
2012-07-08 10:58:53 -05:00
// 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
2023-04-19 14:22:59 +02:00
if( GAMESTATE->m_PlayMode!=PLAY_MODE_REGULAR &&
2012-07-08 10:58:53 -05:00
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; i<m_CurWheelItemData.size(); i++ )
{
if (GAMESTATE->m_SortOrder == SORT_PREFERRED && !GAMESTATE->sLastOpenSection.empty()) {
// old doesn't always have data, use LastOpenSection instead
if( m_CurWheelItemData[i]->m_sText == GAMESTATE->sLastOpenSection)
{
m_iSelection=i;
break;
}
} else if( m_CurWheelItemData[i] == old )
2012-07-08 10:58:53 -05:00
{
m_iSelection=i;
break;
}
}
}
RebuildWheelItems();
}
void MusicWheel::GetCurrentSections(std::vector<RString> &sections)
2020-05-26 22:36:53 -05:00
{
std::vector<MusicWheelItemData *> &wiWheelItems = getWheelItemsData(GAMESTATE->m_SortOrder);
2020-05-26 22:36:53 -05:00
for( unsigned i = 0; i < wiWheelItems.size(); i++ )
{
if ( wiWheelItems[i]->m_Type == WheelItemDataType_Section && !wiWheelItems[i]->m_sText.empty())
sections.push_back(wiWheelItems[i]->m_sText);
}
}
2012-07-08 10:58:53 -05:00
// sm-ssc additions: jump to group
RString MusicWheel::JumpToNextGroup()
{
// Thanks to Juanelote for this logic:
if( HIDE_INACTIVE_SECTIONS )
{
//todo: make it work with other sort types
unsigned iNumGroups = SONGMAN->GetNumSongGroups();
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()
{
// If someone joins, there may be songs on the wheel that should not be
// selectable, or there may be songs that have become selectable.
// Set the status of all the wheel item data vectors to invalid so that
// readyWheelItemsData will rebuild all the data next time
// getWheelItemsData is called for that SortOrder. SetOpenSection calls
// readyWheelItemsData to get the items, and RebuildWheelItems when its
// done, so invalidating and calling SetOpenSection is all we need to do.
// -Kyz
// Also removed the weird checks for course mode and autogen because
// it seems weird that courses wouldn't also be affected by a player
// joining, and not doing it in autogen causes other weird problems. -Kyz
FOREACH_ENUM(SortOrder, so)
2012-07-08 10:58:53 -05:00
{
m_WheelItemDatasStatus[so] = INVALID;
2012-07-08 10:58:53 -05:00
}
SetOpenSection(m_sExpandedSectionName);
2012-07-08 10:58:53 -05:00
}
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
2023-04-19 14:22:59 +02:00
* each player. Prefer songs in the active group, if any.
2012-07-08 10:58:53 -05:00
*
* 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()
{
2023-04-19 14:22:59 +02:00
// probe to find a song that has the preferred
2012-07-08 10:58:53 -05:00
// difficulties of each player
std::vector<Difficulty> vDifficultiesToRequire;
2012-07-08 10:58:53 -05:00
FOREACH_HumanPlayer(p)
{
if( GAMESTATE->m_PreferredDifficulty[p] == Difficulty_Invalid )
continue; // skip
2023-04-19 14:22:59 +02:00
// 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
2012-07-08 10:58:53 -05:00
// 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;
std::vector<MusicWheelItemData *> &wid = getWheelItemsData(GAMESTATE->m_SortOrder);
2012-07-08 10:58:53 -05:00
StepsType st = GAMESTATE->GetCurrentStyle(PLAYER_INVALID)->m_StepsType;
2012-07-08 10:58:53 -05:00
#define NUM_PROBES 1000
for( int i=0; i<NUM_PROBES; i++ )
{
2013-01-24 22:54:02 -05:00
bool isValid = true;
2012-07-08 10:58:53 -05:00
/* Maintaining difficulties is higher priority than maintaining
* the current group. */
if( i == NUM_PROBES/4 )
sPreferredGroup = "";
if( i == NUM_PROBES/2 )
vDifficultiesToRequire.clear();
int iSelection = RandomInt( wid.size() );
if( wid[iSelection]->m_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;
2013-05-01 21:55:47 -04:00
isValid = std::none_of(vDifficultiesToRequire.begin(), vDifficultiesToRequire.end(), [&](Difficulty const &d) {
return !pSong->HasStepsTypeAndDifficulty(st, d);
});
2013-01-24 22:54:02 -05:00
if (isValid)
{
return wid[iSelection]->m_pSong;
}
2012-07-08 10:58:53 -05:00
}
LuaHelpers::ReportScriptError( "Couldn't find any songs" );
2012-07-08 10:58:53 -05:00
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<MusicWheel>
{
public:
2013-03-24 00:12:17 -04:00
static int ChangeSort( T* p, lua_State *L )
{
if( lua_isnil(L,1) ) { lua_pushboolean( L, false ); }
else
{
SortOrder so = Enum::Check<SortOrder>(L, 1);
lua_pushboolean( L, p->ChangeSort( so ) );
}
return 1;
}
2014-07-31 14:57:00 -06:00
DEFINE_METHOD(GetSelectedSection, GetSelectedSection());
2020-05-26 22:36:53 -05:00
static int GetCurrentSections( T* p, lua_State *L )
{
std::vector<RString> v;
2020-05-26 22:36:53 -05:00
p->GetCurrentSections(v);
LuaHelpers::CreateTableFromArray<RString>( v, L );
return 1;
}
2012-07-08 10:58:53 -05:00
static int IsRouletting( T* p, lua_State *L ){ lua_pushboolean( L, p->IsRouletting() ); return 1; }
static int SelectSong( T* p, lua_State *L )
{
2012-07-22 12:26:43 -05:00
if( lua_isnil(L,1) ) { lua_pushboolean( L, false ); }
else
{
Song *pS = Luna<Song>::check( L, 1, true );
lua_pushboolean( L, p->SelectSong( pS ) );
}
return 1;
}
static int SelectCourse( T* p, lua_State *L )
{
2012-07-22 12:26:43 -05:00
if( lua_isnil(L,1) ) { lua_pushboolean( L, false ); }
else
{
Course *pC = Luna<Course>::check( L, 1, true );
2013-06-08 22:28:10 -04:00
lua_pushboolean( L, p->SelectCourse( pC ) );
}
return 1;
}
static int Move(T* p, lua_State *L)
{
if (lua_isnil(L, 1)) { p->Move(0); }
else
{
p->Move(IArg(1));
}
return 1;
}
2012-07-08 10:58:53 -05:00
LunaMusicWheel()
{
2013-03-24 00:12:17 -04:00
ADD_METHOD( ChangeSort );
2014-07-31 14:57:00 -06:00
ADD_METHOD( GetSelectedSection );
2012-07-08 10:58:53 -05:00
ADD_METHOD( IsRouletting );
ADD_METHOD( SelectSong );
ADD_METHOD( SelectCourse );
ADD_METHOD( Move );
2020-05-26 22:36:53 -05:00
ADD_METHOD( GetCurrentSections );
2012-07-08 10:58:53 -05:00
}
};
2013-03-24 00:01:40 -04:00
LUA_REGISTER_DERIVED_CLASS( MusicWheel, WheelBase )
// lua end
2012-07-08 10:58:53 -05:00
/*
* (c) 2001-2004 Chris Danford, Chris Gomez, Glenn Maynard
* All rights reserved.
2023-04-19 14:22:59 +02:00
*
2012-07-08 10:58:53 -05:00
* 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.
2023-04-19 14:22:59 +02:00
*
2012-07-08 10:58:53 -05:00
* 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.
*/