#include "global.h" #include "ScreenOptionsMaster.h" #include "RageException.h" #include "RageUtil.h" #include "RageLog.h" #include "ThemeManager.h" #include "GameState.h" #include "ScreenManager.h" #include "NoteSkinManager.h" #include "Course.h" #include "Steps.h" #include "StyleDef.h" #include "song.h" #include "SongManager.h" #include "Character.h" #include "PrefsManager.h" #include "ScreenOptionsMasterPrefs.h" #include "RageSounds.h" #include "StepMania.h" #include "RageSoundManager.h" #include "ProfileManager.h" #define OPTION_MENU_FLAGS THEME->GetMetric (m_sName,"OptionMenuFlags") #define ROW_LINE(i) THEME->GetMetric (m_sName,ssprintf("Line%i",(i+1))) #define ENTRY(s) THEME->GetMetric ("ScreenOptionsMaster",s) #define ENTRY_NAME(s) THEME->GetMetric ("OptionNames", s) #define ENTRY_MODE(s,i) THEME->GetMetric ("ScreenOptionsMaster",ssprintf("%s,%i",(s).c_str(),(i+1))) #define ENTRY_DEFAULT(s) THEME->GetMetric ("ScreenOptionsMaster",(s) + "Default") #define NEXT_SCREEN THEME->GetMetric (m_sName,"NextScreen") // #define NEXT_SCREEN( play_mode ) THEME->GetMetric (m_sName,"NextScreen"+Capitalize(PlayModeToString(play_mode))) #define PREV_SCREEN THEME->GetMetric (m_sName,"PrevScreen") // #define PREV_SCREEN( play_mode ) THEME->GetMetric (m_sName,"PrevScreen"+Capitalize(PlayModeToString(play_mode))) /* Add the list named "ListName" to the given row/handler. */ void ScreenOptionsMaster::SetList( OptionRowData &row, OptionRowHandler &hand, CString ListName, CString &TitleOut ) { hand.type = ROW_LIST; TitleOut = ListName; if( !ListName.CompareNoCase("noteskins") ) { hand.Default.Init(); /* none */ row.bOneChoiceForAllPlayers = false; CStringArray arraySkinNames; NOTESKIN->GetNoteSkinNames( arraySkinNames ); for( unsigned skin=0; skinm_bEditing ) { row.choices.push_back( "" ); hand.ListEntries.push_back( ModeChoice() ); } else if( GAMESTATE->IsCourseMode() ) // playing a course { row.bOneChoiceForAllPlayers = PREFSMAN->m_bLockCourseDifficulties; Course* pCourse = GAMESTATE->m_pCurCourse; StepsType st = GAMESTATE->GetCurrentStyleDef()->m_StepsType; for( int d=0; dHasCourseDifficulty(st,cd) ) { CString sDifficulty = CourseDifficultyToString( cd ); sDifficulty = Capitalize( sDifficulty ); row.choices.push_back( ENTRY_NAME(sDifficulty+"Courses") ); ModeChoice mc; mc.m_CourseDifficulty = cd; hand.ListEntries.push_back( mc ); } } } else // !GAMESTATE->IsCourseMode(), playing a song { vector vSteps; GAMESTATE->m_pCurSong->GetSteps( vSteps, GAMESTATE->GetCurrentStyleDef()->m_StepsType ); SortNotesArrayByDifficulty( vSteps ); for( unsigned i=0; iGetDifficultyThemeName( pSteps->GetDifficulty() ); s += ssprintf( " (%d)", pSteps->GetMeter() ); row.choices.push_back( s ); ModeChoice mc; mc.m_pSteps = pSteps; mc.m_dc = pSteps->GetDifficulty(); hand.ListEntries.push_back( mc ); } } } /* Add the given configuration value to the given row/handler. */ void ScreenOptionsMaster::SetConf( OptionRowData &row, OptionRowHandler &hand, CString param, CString &TitleOut ) { /* Configuration values are never per-player. */ row.bOneChoiceForAllPlayers = true; hand.type = ROW_CONFIG; hand.opt = ConfOption::Find( param ); if( hand.opt == NULL ) RageException::Throw( "Invalid Conf type \"%s\"", param.c_str() ); hand.opt->MakeOptionsList( row.choices ); TitleOut = hand.opt->name; } /* Add a list of available characters to the given row/handler. */ void ScreenOptionsMaster::SetCharacter( OptionRowData &row, OptionRowHandler &hand ) { hand.type = ROW_CHARACTER; row.bOneChoiceForAllPlayers = false; hand.Default.m_pCharacter = GAMESTATE->GetDefaultCharacter(); { row.choices.push_back( ENTRY_NAME("Off") ); ModeChoice mc; mc.m_pCharacter = GAMESTATE->GetDefaultCharacter(); hand.ListEntries.push_back( mc ); } vector apCharacters; GAMESTATE->GetCharacters( apCharacters ); for( unsigned i=0; im_sName; s.MakeUpper(); row.choices.push_back( s ); ModeChoice mc; mc.m_pCharacter = pCharacter; hand.ListEntries.push_back( mc ); } } /* Add a list of available characters to the given row/handler. */ void ScreenOptionsMaster::SetSaveToProfile( OptionRowData &row, OptionRowHandler &hand ) { hand.type = ROW_SAVE_TO_PROFILE; row.bOneChoiceForAllPlayers = false; row.choices.push_back( ENTRY_NAME("Don'tSave") ); row.choices.push_back( ENTRY_NAME("SaveToProfile") ); } ScreenOptionsMaster::ScreenOptionsMaster( CString sClassName ): ScreenOptions( sClassName ) { LOG->Trace("ScreenOptionsMaster::ScreenOptionsMaster(%s)", m_sName.c_str() ); /* If this file doesn't exist, leave the music alone (eg. ScreenPlayerOptions music sample * left over from ScreenSelectMusic). If you really want to play no music, add a redir * to _silent. */ CString MusicPath = THEME->GetPathToS( ssprintf("%s music", m_sName.c_str()), true ); if( MusicPath != "" ) SOUND->PlayMusic( MusicPath ); CStringArray Flags; split( OPTION_MENU_FLAGS, ";", Flags, true ); InputMode im = INPUTMODE_INDIVIDUAL; bool Explanations = false; int NumRows = -1; unsigned i; for( i = 0; i < Flags.size(); ++i ) { Flags[i].MakeLower(); if( sscanf( Flags[i], "rows,%i", &NumRows ) == 1 ) continue; if( Flags[i] == "together" ) im = INPUTMODE_TOGETHER; if( Flags[i] == "explanations" ) Explanations = true; if( Flags[i] == "forceallplayers" ) { for( int pn=0; pnm_bSideIsJoined[pn] = true; GAMESTATE->m_MasterPlayerNumber = PlayerNumber(0); } if( Flags[i] == "smnavigation" ) SetNavigation( NAV_THREE_KEY_MENU ); if( Flags[i] == "firstchoicegoesdown" ) SetNavigation( NAV_FIRST_CHOICE_GOES_DOWN ); } if( NumRows == -1 ) RageException::Throw( "%s::OptionMenuFlags is missing \"rows\" field", m_sName.c_str() ); m_OptionRowAlloc = new OptionRowData[NumRows]; for( i = 0; (int) i < NumRows; ++i ) { OptionRowData &row = m_OptionRowAlloc[i]; CString sRowCommands = ROW_LINE(i); vector vCommands; ParseCommands( sRowCommands, vCommands ); if( vCommands.size() < 1 ) RageException::Throw( "Parse error in %s::Line%i", m_sName.c_str(), i+1 ); OptionRowHandler hand; for( unsigned part = 0; part < vCommands.size(); ++part) { ParsedCommand& command = vCommands[part]; HandleParams; const CString name = sParam(0); if( !name.CompareNoCase("list") ) { SetList( row, hand, sParam(1), row.name ); } else if( !name.CompareNoCase("steps") ) { SetStep( row, hand ); row.name = "Steps"; } else if( !name.CompareNoCase("conf") ) { SetConf( row, hand, sParam(1), row.name ); } else if( !name.CompareNoCase("characters") ) { SetCharacter( row, hand ); row.name = "Characters"; } else if( !name.CompareNoCase("SaveToProfile") ) { SetSaveToProfile( row, hand ); row.name = "Save To\nProfile"; } else RageException::Throw( "Unexpected type '%s' in %s::Line%i", name.c_str(), m_sName.c_str(), i ); CheckHandledParams; } // TRICKY: Insert a down arrow as the first choice in the row. if( m_OptionsNavigation == NAV_FIRST_CHOICE_GOES_DOWN ) { row.choices.insert( row.choices.begin(), ENTRY_NAME("NextRow") ); hand.ListEntries.insert( hand.ListEntries.begin(), ModeChoice() ); } OptionRowHandlers.push_back( hand ); } ASSERT( (int) OptionRowHandlers.size() == NumRows ); Init( im, m_OptionRowAlloc, NumRows ); } ScreenOptionsMaster::~ScreenOptionsMaster() { delete [] m_OptionRowAlloc; } void SelectExactlyOne( int iSelection, vector &vbSelectedOut ) { for( int i=0; i<(int)vbSelectedOut.size(); i++ ) vbSelectedOut[i] = i==iSelection; } void ScreenOptionsMaster::ImportOption( const OptionRowData &row, const OptionRowHandler &hand, int pn, int rowno, vector &vbSelectedOut ) { /* Figure out which selection is the default. */ switch( hand.type ) { case ROW_LIST: case ROW_STEP: case ROW_CHARACTER: { int FallbackOption = -1; bool UseFallbackOption = true; for( unsigned e = 0; e < hand.ListEntries.size(); ++e ) { const ModeChoice &mc = hand.ListEntries[e]; vbSelectedOut[e] = false; if( mc.IsZero() ) { /* The entry has no effect. This is usually a default "none of the * above" entry. It will always return true for DescribesCurrentMode(). * It's only the selected choice if nothing else matches. */ if( !row.bMultiSelect ) FallbackOption = e; continue; } if( row.bOneChoiceForAllPlayers ) { if( mc.DescribesCurrentModeForAllPlayers() ) { UseFallbackOption = false; if( !row.bMultiSelect ) SelectExactlyOne( e, vbSelectedOut ); else vbSelectedOut[e] = true; } } else { if( mc.DescribesCurrentMode( (PlayerNumber) pn) ) { UseFallbackOption = false; if( !row.bMultiSelect ) SelectExactlyOne( e, vbSelectedOut ); else vbSelectedOut[e] = true; } } } if( UseFallbackOption && FallbackOption != -1 && !row.bMultiSelect ) SelectExactlyOne( FallbackOption, vbSelectedOut ); return; } case ROW_CONFIG: { int iSelection = hand.opt->Get( row.choices ); SelectExactlyOne( iSelection+(m_OptionsNavigation==NAV_FIRST_CHOICE_GOES_DOWN?1:0), vbSelectedOut ); return; } case ROW_SAVE_TO_PROFILE: SelectExactlyOne( 0+(m_OptionsNavigation==NAV_FIRST_CHOICE_GOES_DOWN?1:0), vbSelectedOut ); return; default: ASSERT(0); } if( !row.bMultiSelect ) { // The first row ("go down") should not be selected. ASSERT( vbSelectedOut[0] == false ); // there should be exactly one option selected int iNumSelected = 0; for( unsigned e = 1; e < hand.ListEntries.size(); ++e ) if( vbSelectedOut[e] ) iNumSelected++; ASSERT( iNumSelected == 1 ); } } void ScreenOptionsMaster::ImportOptions() { for( unsigned i = 0; i < OptionRowHandlers.size(); ++i ) { const OptionRowHandler &hand = OptionRowHandlers[i]; const OptionRowData &data = m_OptionRowAlloc[i]; Row &row = *m_Rows[i]; if( data.bOneChoiceForAllPlayers ) { ImportOption( data, hand, 0, i, row.m_vbSelected[0] ); } else { for( int p=0; pIsHumanPlayer(p) ) continue; // skip ImportOption( data, hand, p, i, row.m_vbSelected[p] ); } } } } int GetOneSelection( const vector &vbSelected ) { for( unsigned i=0; i &vbSelected ) { /* Figure out which selection is the default. */ switch( hand.type ) { case ROW_LIST: case ROW_CHARACTER: case ROW_STEP: { hand.Default.Apply( (PlayerNumber)pn ); for( unsigned i=0; iGet( row.choices ); /* Apply. */ hand.opt->Put( sel, row.choices ); /* Get the new choice. */ int New = hand.opt->Get( row.choices ); /* If it didn't change, don't return any side-effects. */ if( Original == New ) return 0; return hand.opt->GetEffects(); } break; case ROW_SAVE_TO_PROFILE: { int sel = GetOneSelection(vbSelected) - (m_OptionsNavigation==NAV_FIRST_CHOICE_GOES_DOWN?1:0); if( sel == 1 ) { if( PROFILEMAN->IsUsingProfile((PlayerNumber)pn) ) { Profile* pProfile = PROFILEMAN->GetProfile((PlayerNumber)pn); pProfile->m_bUsingProfileDefaultModifiers = true; pProfile->m_sDefaultModifiers = GAMESTATE->m_PlayerOptions[pn].GetString(); } } } break; default: ASSERT(0); break; } return 0; } void ScreenOptionsMaster::ExportOptions() { int ChangeMask = 0; CHECKPOINT; unsigned i; for( i = 0; i < OptionRowHandlers.size(); ++i ) { CHECKPOINT_M( ssprintf("%i/%i", i, int(OptionRowHandlers.size())) ); const OptionRowHandler &hand = OptionRowHandlers[i]; const OptionRowData &data = m_OptionRowAlloc[i]; Row &row = *m_Rows[i]; for( int p=0; pIsHumanPlayer(p) ) continue; vector &vbSelected = row.m_vbSelected[p]; ChangeMask |= ExportOption( data, hand, p, vbSelected ); } } CHECKPOINT; /* If the selection is on a LIST, and the selected LIST option sets the screen, * honor it. */ m_NextScreen = ""; const int row = this->GetCurrentRow(); if( row != -1 ) { const OptionRowHandler &hand = OptionRowHandlers[row]; if( hand.type == ROW_LIST ) { const int choice = m_Rows[row]->m_iChoiceWithFocus[0]; const ModeChoice &mc = hand.ListEntries[choice]; if( mc.m_sScreen != "" ) m_NextScreen = mc.m_sScreen; } } CHECKPOINT; // NEXT_SCREEN(GAMESTATE->m_PlayMode) ); // XXX: handle different destinations based on play mode? if( m_NextScreen == "" ) m_NextScreen = NEXT_SCREEN; /* Did the theme change? */ if( (ChangeMask & OPT_APPLY_THEME) || (ChangeMask & OPT_APPLY_GRAPHICS) ) // reset graphics to apply new window title and icon ApplyGraphicOptions(); if( ChangeMask & OPT_SAVE_PREFERENCES ) { /* Save preferences. */ LOG->Trace("ROW_CONFIG used; saving ..."); PREFSMAN->SaveGlobalPrefsToDisk(); SaveGamePrefsToDisk(); } if( ChangeMask & OPT_RESET_GAME ) { ResetGame(); m_NextScreen = ""; } if( ChangeMask & OPT_APPLY_SOUND ) { SOUNDMAN->SetPrefs( PREFSMAN->m_fSoundVolume ); } if( ChangeMask & OPT_SAVE_MODIFIERS_TO_PROFILE ) { SOUNDMAN->SetPrefs( PREFSMAN->m_fSoundVolume ); } CHECKPOINT; } void ScreenOptionsMaster::GoToNextState() { if( GAMESTATE->m_bEditing ) SCREENMAN->PopTopScreen(); else if( m_NextScreen != "" ) SCREENMAN->SetNewScreen( m_NextScreen ); } void ScreenOptionsMaster::GoToPrevState() { /* XXX: A better way to handle this would be to check if we're a pushed screen. */ if( GAMESTATE->m_bEditing ) SCREENMAN->PopTopScreen(); // XXX: handle different destinations based on play mode? else SCREENMAN->SetNewScreen( PREV_SCREEN ); // (GAMESTATE->m_PlayMode) ); } void ScreenOptionsMaster::RefreshIcons() { for( int p=0; pIsHumanPlayer(p) ) continue; for( unsigned i=0; iType == Row::ROW_EXIT ) continue; // skip Row &row = *m_Rows[i]; const OptionRowData &data = row.m_RowDef; // find first selection and whether multiple are selected int iFirstSelection = -1; bool bMultipleSelected = false; for( unsigned j=0; j