diff --git a/stepmania/src/SongUtil.cpp b/stepmania/src/SongUtil.cpp new file mode 100644 index 0000000000..9756b52dd0 --- /dev/null +++ b/stepmania/src/SongUtil.cpp @@ -0,0 +1,370 @@ +#include "global.h" +/* +----------------------------------------------------------------------------- + Class: SongUtil + + Desc: See header. + + Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. + Chris Danford + Glenn Maynard +----------------------------------------------------------------------------- +*/ + +#include "SongUtil.h" +#include "song.h" +#include "Steps.h" +#include "GameState.h" +#include "StyleDef.h" +#include "ProfileManager.h" +#include "PrefsManager.h" +#include "SongManager.h" + + +///////////////////////////////////// +// Sorting +///////////////////////////////////// + +/* Just calculating GetNumTimesPlayed within the sort is pretty slow, so let's precompute + * it. (This could be generalized with a template.) */ +map song_sort_val; + +bool CompareSongPointersBySortValueAscending(const Song *pSong1, const Song *pSong2) +{ + return song_sort_val[pSong1] < song_sort_val[pSong2]; +} + +bool CompareSongPointersBySortValueDescending(const Song *pSong1, const Song *pSong2) +{ + return song_sort_val[pSong1] > song_sort_val[pSong2]; +} + + +CString SongUtil::MakeSortString( CString s ) +{ + s.MakeUpper(); + + // Make sure that non-alphanumeric characters are placed at the very end + if( s.size() > 0 ) + { + if( s[0] == '.' ) // ".59" + s.erase(s.begin()); + if( (s[0] < 'A' || s[0] > 'Z') && (s[0] < '0' || s[0] > '9') ) + s = char(126) + s; + } + + return s; +} + +bool CompareSongPointersByTitle(const Song *pSong1, const Song *pSong2) +{ + // Prefer transliterations to full titles + CString s1 = pSong1->GetTranslitMainTitle(); + CString s2 = pSong2->GetTranslitMainTitle(); + if( s1 == s2 ) + { + s1 = pSong1->GetTranslitSubTitle(); + s2 = pSong2->GetTranslitSubTitle(); + } + + s1 = SongUtil::MakeSortString(s1); + s2 = SongUtil::MakeSortString(s2); + + int ret = s1.CompareNoCase( s2 ); + if(ret < 0) return true; + if(ret > 0) return false; + + /* The titles are the same. Ensure we get a consistent ordering + * by comparing the unique SongFilePaths. */ + return pSong1->GetSongFilePath().CompareNoCase(pSong2->GetSongFilePath()) < 0; +} + +void SongUtil::SortSongPointerArrayByTitle( vector &arraySongPointers ) +{ + sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByTitle ); +} + +static int GetSongSortDifficulty(const Song *pSong) +{ + vector aNotes; + pSong->GetSteps( aNotes, GAMESTATE->GetCurrentStyleDef()->m_StepsType ); + + /* Sort by the first difficulty found in the following order: */ + const Difficulty d[] = { DIFFICULTY_EASY, DIFFICULTY_MEDIUM, DIFFICULTY_HARD, + DIFFICULTY_CHALLENGE, DIFFICULTY_INVALID }; + + for(int i = 0; d[i] != DIFFICULTY_INVALID; ++i) + { + for( unsigned j = 0; j < aNotes.size(); ++j) + { + if(aNotes[j]->GetDifficulty() != d[i]) + continue; + + return aNotes[j]->GetMeter(); + } + } + + return 1; +} + +void SongUtil::SortSongPointerArrayByDifficulty( vector &arraySongPointers ) +{ + for( unsigned i = 0; i < arraySongPointers.size(); ++i ) + song_sort_val[arraySongPointers[i]] = + ssprintf("%9i", GetSongSortDifficulty(arraySongPointers[i])); + stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueAscending ); +} + +bool CompareSongPointersByBPM(const Song *pSong1, const Song *pSong2) +{ + float fMinBPM1, fMaxBPM1, fMinBPM2, fMaxBPM2; + pSong1->GetDisplayBPM( fMinBPM1, fMaxBPM1 ); + pSong2->GetDisplayBPM( fMinBPM2, fMaxBPM2 ); + + if( fMaxBPM1 < fMaxBPM2 ) + return true; + if( fMaxBPM1 > fMaxBPM2 ) + return false; + + return CompareCStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() ); +} + +void SongUtil::SortSongPointerArrayByBPM( vector &arraySongPointers ) +{ + sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByBPM ); +} + +void AppendOctal( int n, int digits, CString &out ) +{ + for( int p = digits-1; p >= 0; --p ) + { + const int shift = p*3; + int n2 = (n >> shift) & 0x7; + out.insert( out.end(), (char) (n2+'0') ); + } +} + +bool CompDescending( const pair &a, const pair &b ) +{ return a.second > b.second; } + +void SongUtil::SortSongPointerArrayByGrade( vector &arraySongPointers ) +{ + /* Optimize by pre-writing a string to compare, since doing GetNumNotesWithGrade + * inside the sort is too slow. */ + typedef pair< Song *, CString > val; + vector vals; + vals.reserve( arraySongPointers.size() ); + + unsigned i; + for( i = 0; i < arraySongPointers.size(); ++i ) + { + Song *pSong = arraySongPointers[i]; + + CString foo; + foo.reserve(256); + for( int g=GRADE_TIER_1; g<=GRADE_NO_DATA; ++g ) + { + int n = pSong->GetNumNotesWithGrade( (Grade)g ); + AppendOctal( n, 3, foo ); + } + vals.push_back( val(pSong, foo) ); + } + + sort( vals.begin(), vals.end(), CompDescending ); + + for( i = 0; i < arraySongPointers.size(); ++i ) + arraySongPointers[i] = vals[i].first; +} + + +void SongUtil::SortSongPointerArrayByArtist( vector &arraySongPointers ) +{ + for( unsigned i = 0; i < arraySongPointers.size(); ++i ) + song_sort_val[arraySongPointers[i]] = MakeSortString( arraySongPointers[i]->GetTranslitArtist() ); + stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueAscending ); +} + +static int CompareSongPointersByGroup(const Song *pSong1, const Song *pSong2) +{ + return pSong1->m_sGroupName < pSong2->m_sGroupName; +} + +void SongUtil::SortSongPointerArrayByGroupAndDifficulty( vector &arraySongPointers ) +{ + SortSongPointerArrayByDifficulty( arraySongPointers ); + stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByGroup ); +} + +int CompareSongPointersByGroupAndTitle(const Song *pSong1, const Song *pSong2) +{ + const CString &sGroup1 = pSong1->m_sGroupName; + const CString &sGroup2 = pSong2->m_sGroupName; + + if( sGroup1 < sGroup2 ) + return true; + if( sGroup1 > sGroup2 ) + return false; + + /* Same group; compare by name. */ + return CompareSongPointersByTitle( pSong1, pSong2 ); +} + +void SongUtil::SortSongPointerArrayByGroupAndTitle( vector &arraySongPointers ) +{ + sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersByGroupAndTitle ); +} + +void SongUtil::SortSongPointerArrayByNumPlays( vector &arraySongPointers, ProfileSlot slot, bool bDescending ) +{ + Profile* pProfile = PROFILEMAN->GetProfile(slot); + if( pProfile == NULL ) + return; // nothing to do since we don't have data + SortSongPointerArrayByNumPlays( arraySongPointers, pProfile, bDescending ); +} + +void SongUtil::SortSongPointerArrayByNumPlays( vector &arraySongPointers, const Profile* pProfile, bool bDescending ) +{ + ASSERT( pProfile ); + for(unsigned i = 0; i < arraySongPointers.size(); ++i) + song_sort_val[arraySongPointers[i]] = ssprintf("%9i", pProfile->GetSongNumTimesPlayed(arraySongPointers[i])); + stable_sort( arraySongPointers.begin(), arraySongPointers.end(), bDescending ? CompareSongPointersBySortValueDescending : CompareSongPointersBySortValueAscending ); + song_sort_val.clear(); +} + +CString SongUtil::GetSectionNameFromSongAndSort( const Song* pSong, SortOrder so ) +{ + if( pSong == NULL ) + return ""; + + switch( so ) + { + case SORT_PREFERRED: + return ""; + case SORT_GROUP: + return pSong->m_sGroupName; + case SORT_TITLE: + case SORT_ARTIST: + { + CString s; + switch( so ) + { + case SORT_TITLE: s = pSong->GetTranslitMainTitle(); break; + case SORT_ARTIST: s = pSong->GetTranslitArtist(); break; + default: ASSERT(0); + } + s = MakeSortString(s); // resulting string will be uppercase + + if( s.empty() ) + return ""; + else if( s[0] >= '0' && s[0] <= '9' ) + return "NUM"; + else if( s[0] < 'A' || s[0] > 'Z') + return "OTHER"; + else + return s.Left(1); + } + case SORT_BPM: + { + const int iBPMGroupSize = 20; + float fMinBPM, fMaxBPM; + pSong->GetDisplayBPM( fMinBPM, fMaxBPM ); + int iMaxBPM = (int)fMaxBPM; + iMaxBPM += iBPMGroupSize - (iMaxBPM%iBPMGroupSize) - 1; + return ssprintf("%03d-%03d",iMaxBPM-(iBPMGroupSize-1), iMaxBPM); + } + case SORT_MOST_PLAYED: + return ""; + case SORT_GRADE: + { + for( int i=GRADE_TIER_1; iGetNumNotesWithGrade( g ); + if( iCount > 0 ) + return ssprintf( "%4s x %d", GradeToThemedString(g).c_str(), iCount ); + } + return GradeToThemedString( GRADE_NO_DATA ); + } + case SORT_EASY_METER: + { + Steps* pNotes = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyleDef()->m_StepsType,DIFFICULTY_EASY); + if( pNotes ) + return ssprintf("%02d", pNotes->GetMeter() ); + return "N/A"; + } + case SORT_MEDIUM_METER: + { + Steps* pNotes = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyleDef()->m_StepsType,DIFFICULTY_MEDIUM); + if( pNotes ) + return ssprintf("%02d", pNotes->GetMeter() ); + return "N/A"; + } + case SORT_HARD_METER: + { + Steps* pNotes = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyleDef()->m_StepsType,DIFFICULTY_HARD); + if( pNotes ) + return ssprintf("%02d", pNotes->GetMeter() ); + return "N/A"; + } + case SORT_CHALLENGE_METER: + { + Steps* pNotes = pSong->GetStepsByDifficulty(GAMESTATE->GetCurrentStyleDef()->m_StepsType,DIFFICULTY_CHALLENGE); + if( pNotes ) + return ssprintf("%02d", pNotes->GetMeter() ); + return "N/A"; + } + case SORT_SORT_MENU: + case SORT_MODE_MENU: + return ""; + case SORT_ALL_COURSES: + case SORT_NONSTOP_COURSES: + case SORT_ONI_COURSES: + case SORT_ENDLESS_COURSES: + default: + ASSERT(0); + return ""; + } +} + +void SongUtil::SortSongPointerArrayBySectionName( vector &arraySongPointers, SortOrder so ) +{ + for(unsigned i = 0; i < arraySongPointers.size(); ++i) + { + CString val = GetSectionNameFromSongAndSort( arraySongPointers[i], so ); + + /* Make sure NUM comes first and OTHER comes last. */ + if( val == "NUM" ) val = "0"; + else if( val == "OTHER" ) val = "2"; + else val = "1" + val; + + song_sort_val[arraySongPointers[i]] = val; + } + + stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueAscending ); + song_sort_val.clear(); +} + +void SongUtil::SortSongPointerArrayByMeter( vector &arraySongPointers, Difficulty dc ) +{ + song_sort_val.clear(); + for(unsigned i = 0; i < arraySongPointers.size(); ++i) + { + Steps* pNotes = arraySongPointers[i]->GetStepsByDifficulty( GAMESTATE->GetCurrentStyleDef()->m_StepsType, dc ); + song_sort_val[arraySongPointers[i]] = ssprintf("%i", pNotes ? pNotes->GetMeter() : 0); + } + stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueAscending ); +} + + +void SongID::FromSong( const Song *p ) +{ + if( p ) + sDir = p->GetSongDir(); + else + sDir = ""; +} + +Song *SongID::ToSong() const +{ + return SONGMAN->GetSongFromDir( sDir ); +} diff --git a/stepmania/src/SongUtil.h b/stepmania/src/SongUtil.h new file mode 100644 index 0000000000..3005ce2928 --- /dev/null +++ b/stepmania/src/SongUtil.h @@ -0,0 +1,47 @@ +#ifndef SONGUTIL_H +#define SONGUTIL_H +/* +----------------------------------------------------------------------------- + Class: SongUtil + + Desc: Utility functions that deal with Song. + + Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. + Chris Danford +----------------------------------------------------------------------------- +*/ + +#include "GameConstantsAndTypes.h" + +class Song; +class Profile; + +namespace SongUtil +{ + CString MakeSortString( CString s ); + void SortSongPointerArrayByDifficulty( vector &arraySongPointers ); + void SortSongPointerArrayByTitle( vector &arraySongPointers ); + void SortSongPointerArrayByBPM( vector &arraySongPointers ); + void SortSongPointerArrayByGrade( vector &arraySongPointers ); + void SortSongPointerArrayByArtist( vector &arraySongPointers ); + void SortSongPointerArrayByGroupAndDifficulty( vector &arraySongPointers ); + void SortSongPointerArrayByGroupAndTitle( vector &arraySongPointers ); + void SortSongPointerArrayByNumPlays( vector &arraySongPointers, ProfileSlot slot, bool bDescending ); + void SortSongPointerArrayByNumPlays( vector &arraySongPointers, const Profile* pProfile, bool bDescending ); + void SortSongPointerArrayByMeter( vector &arraySongPointers, Difficulty dc ); + CString GetSectionNameFromSongAndSort( const Song* pSong, SortOrder so ); + void SortSongPointerArrayBySectionName( vector &arraySongPointers, SortOrder so ); +} + +class SongID +{ + CString sDir; + +public: + SongID() { FromSong(NULL); } + void FromSong( const Song *p ); + Song *ToSong() const; +}; + + +#endif diff --git a/stepmania/src/StepsUtil.cpp b/stepmania/src/StepsUtil.cpp new file mode 100644 index 0000000000..6027ba60c9 --- /dev/null +++ b/stepmania/src/StepsUtil.cpp @@ -0,0 +1,136 @@ +#include "global.h" +/* +----------------------------------------------------------------------------- + Class: StepsUtil + + Desc: See header. + + Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. + Chris Danford + Glenn Maynard + David Wilson +----------------------------------------------------------------------------- +*/ + +#include "StepsUtil.h" +#include "Steps.h" +#include "ProfileManager.h" +#include "song.h" + +// +// Sorting stuff +// +map steps_sort_val; + +bool CompareStepsPointersBySortValueAscending(const Steps *pSteps1, const Steps *pSteps2) +{ + return steps_sort_val[pSteps1] < steps_sort_val[pSteps2]; +} + +bool CompareStepsPointersBySortValueDescending(const Steps *pSteps1, const Steps *pSteps2) +{ + return steps_sort_val[pSteps1] > steps_sort_val[pSteps2]; +} + +void StepsUtil::SortStepsPointerArrayByNumPlays( vector &vStepsPointers, ProfileSlot slot, bool bDescending ) +{ + Profile* pProfile = PROFILEMAN->GetProfile(slot); + if( pProfile == NULL ) + return; // nothing to do since we don't have data + SortStepsPointerArrayByNumPlays( vStepsPointers, pProfile, bDescending ); +} + +void StepsUtil::SortStepsPointerArrayByNumPlays( vector &vStepsPointers, const Profile* pProfile, bool bDecending ) +{ + ASSERT( pProfile ); + for(unsigned i = 0; i < vStepsPointers.size(); ++i) + steps_sort_val[vStepsPointers[i]] = ssprintf("%9i", pProfile->GetStepsNumTimesPlayed(vStepsPointers[i])); + stable_sort( vStepsPointers.begin(), vStepsPointers.end(), bDecending ? CompareStepsPointersBySortValueDescending : CompareStepsPointersBySortValueAscending ); + steps_sort_val.clear(); +} + +bool StepsUtil::CompareNotesPointersByRadarValues(const Steps* pNotes1, const Steps* pNotes2) +{ + float fScore1 = 0; + float fScore2 = 0; + + for( int r=0; rGetRadarValues()[r]; + fScore2 += pNotes2->GetRadarValues()[r]; + } + + return fScore1 < fScore2; +} + +bool StepsUtil::CompareNotesPointersByMeter(const Steps *pNotes1, const Steps* pNotes2) +{ + return pNotes1->GetMeter() < pNotes2->GetMeter(); +} + +bool StepsUtil::CompareNotesPointersByDifficulty(const Steps *pNotes1, const Steps *pNotes2) +{ + return pNotes1->GetDifficulty() < pNotes2->GetDifficulty(); +} + +void StepsUtil::SortNotesArrayByDifficulty( vector &arraySteps ) +{ + /* Sort in reverse order of priority. */ + stable_sort( arraySteps.begin(), arraySteps.end(), CompareNotesPointersByRadarValues ); + stable_sort( arraySteps.begin(), arraySteps.end(), CompareNotesPointersByMeter ); + stable_sort( arraySteps.begin(), arraySteps.end(), CompareNotesPointersByDifficulty ); +} + +bool StepsUtil::CompareStepsPointersByTypeAndDifficulty(const Steps *pStep1, const Steps *pStep2) +{ + if( pStep1->m_StepsType < pStep2->m_StepsType ) + return true; + if( pStep1->m_StepsType > pStep2->m_StepsType ) + return false; + return pStep1->GetDifficulty() < pStep2->GetDifficulty(); +} + +void StepsUtil::SortStepsByTypeAndDifficulty( vector &arraySongPointers ) +{ + sort( arraySongPointers.begin(), arraySongPointers.end(), CompareStepsPointersByTypeAndDifficulty ); +} + +void StepsID::FromSteps( const Steps *p ) +{ + if( p == NULL ) + { + st = STEPS_TYPE_INVALID; + dc = DIFFICULTY_INVALID; + sDescription = ""; + } + else + { + st = p->m_StepsType; + dc = p->GetDifficulty(); + sDescription = p->GetDescription(); + } +} + +/* XXX: Don't allow duplicate edit descriptions, and don't allow edit descriptions + * to be difficulty names (eg. "Hard"). If we do that, this will be completely unambiguous. + * + * XXX: Unless two memcards are inserted and there's overlap in the names. In that + * case, maybe both edits should be renamed to "Pn: foo"; as long as we don't write + * them back out (which we don't do except in the editor), it won't be permanent. + * We could do this during the actual Steps::GetID() call, instead, but then it'd have + * to have access to Song::m_LoadedFromProfile. */ +Steps *StepsID::ToSteps( const Song *p, bool bAllowNull ) const +{ + if( st == STEPS_TYPE_INVALID || dc == DIFFICULTY_INVALID || sDescription.empty() ) + return NULL; + + vector vNotes; + p->GetSteps( vNotes, st, dc, -1, -1, sDescription, true ); + if( !vNotes.empty() ) + return vNotes[0]; + if( bAllowNull ) + return NULL; + RageException::Throw( "%i, %i, \"%s\"", st, dc, sDescription.c_str() ); +} + + diff --git a/stepmania/src/StepsUtil.h b/stepmania/src/StepsUtil.h new file mode 100644 index 0000000000..a0f5fa1e4e --- /dev/null +++ b/stepmania/src/StepsUtil.h @@ -0,0 +1,46 @@ +#ifndef STEPSUTIL_H +#define STEPSUTIL_H +/* +----------------------------------------------------------------------------- + Class: StepsUtil + + Desc: Holds note information for a Song. A Song may have one or more Notess. + A Steps can be played by one or more Styles. See StyleDef.h for more info on + Styles. + + Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. + Chris Danford +----------------------------------------------------------------------------- +*/ + +#include "GameConstantsAndTypes.h" + +class Steps; +class Song; +class Profile; + +namespace StepsUtil +{ + bool CompareNotesPointersByRadarValues(const Steps* pNotes1, const Steps* pNotes2); + bool CompareNotesPointersByMeter(const Steps *pNotes1, const Steps* pNotes2); + bool CompareNotesPointersByDifficulty(const Steps *pNotes1, const Steps *pNotes2); + void SortNotesArrayByDifficulty( vector &arrayNotess ); + bool CompareStepsPointersByTypeAndDifficulty(const Steps *pStep1, const Steps *pStep2); + void SortStepsByTypeAndDifficulty( vector &arraySongPointers ); + void SortStepsPointerArrayByNumPlays( vector &vStepsPointers, ProfileSlot slot, bool bDescending ); + void SortStepsPointerArrayByNumPlays( vector &vStepsPointers, const Profile* pProfile, bool bDescending ); +}; + +class StepsID +{ + StepsType st; + Difficulty dc; + CString sDescription; + +public: + StepsID() { FromSteps(NULL); } + void FromSteps( const Steps *p ); + Steps *ToSteps( const Song *p, bool bAllowNull ) const; +}; + +#endif