diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index 01f0d32fc9..8a3db08ff0 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -1,1438 +1,1429 @@ -#include "global.h" -#include "OptionRowHandler.h" -#include "LuaManager.h" -#include "ScreenOptionsMasterPrefs.h" -#include "NoteSkinManager.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "GameState.h" -#include "Course.h" -#include "Steps.h" -#include "Style.h" -#include "Song.h" -#include "SongManager.h" -#include "Character.h" -#include "PrefsManager.h" -#include "SongUtil.h" -#include "StepsUtil.h" -#include "GameManager.h" -#include "Foreach.h" -#include "GameSoundManager.h" -#include "CommonMetrics.h" -#include "CharacterManager.h" -#include "ScreenManager.h" -#include "ScreenMiniMenu.h" // for MenuRowDef -#include "FontCharAliases.h" - -#define ENTRY(s) THEME->GetMetric ("ScreenOptionsMaster",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 NOTE_SKIN_SORT_ORDER THEME->GetMetric ("ScreenOptionsMaster","NoteSkinSortOrder") -#define STEPS_ROW_LAYOUT_TYPE THEME->GetMetric("ScreenOptionsMaster","StepsRowLayoutType") -#define STEPS_USE_CHART_NAME THEME->GetMetricB("ScreenOptionsMaster","StepsUseChartName") - -static const char *SelectTypeNames[] = { - "SelectOne", - "SelectMultiple", - "SelectNone", -}; -XToString( SelectType ); -StringToX( SelectType ); -LuaXType( SelectType ); - -static const char *LayoutTypeNames[] = { - "ShowAllInRow", - "ShowOneInRow", -}; -XToString( LayoutType ); -StringToX( LayoutType ); -LuaXType( LayoutType ); - -RString OptionRowHandler::OptionTitle() const -{ - bool bTheme = false; - - // HACK: Always theme the NEXT_ROW and EXIT items, even if metrics says not to theme. - if( m_Def.m_bAllowThemeTitle ) - bTheme = true; - - RString s = m_Def.m_sName; - if( s.empty() ) - return s; - - return bTheme ? THEME->GetString("OptionTitles",s) : s; -} - -RString OptionRowHandler::GetThemedItemText( int iChoice ) const -{ - RString s = m_Def.m_vsChoices[iChoice]; - if( s == "" ) - return ""; - bool bTheme = false; - - if( m_Def.m_bAllowThemeItems ) bTheme = true; - - // Items beginning with a pipe mean "don't theme". - // This allows us to disable theming on a per-choice basis for choice names that are just a number - // and don't need to be localized. - if( s[0] == '|' ) - { - s.erase( s.begin() ); - bTheme = false; - } - - if( bTheme ) - s = CommonMetrics::LocalizeOptionItem( s, false ); - return s; -} - -void OptionRowHandler::GetIconTextAndGameCommand( int iFirstSelection, RString &sIconTextOut, GameCommand &gcOut ) const -{ - sIconTextOut = ""; - gcOut.Init(); -} - -void OptionRowHandlerUtil::SelectExactlyOne( int iSelection, vector &vbSelectedOut ) -{ - ASSERT_M( iSelection >= 0 && iSelection < (int) vbSelectedOut.size(), - ssprintf("%d/%u",iSelection, unsigned(vbSelectedOut.size())) ); - for( int i=0; i &vbSelected ) -{ - int iRet = -1; - for( unsigned i=0; i m_aListEntries; - GameCommand m_Default; - bool m_bUseModNameForIcon; - vector m_vsBroadcastOnExport; - - OptionRowHandlerList() { Init(); } - virtual void Init() - { - OptionRowHandler::Init(); - m_aListEntries.clear(); - m_Default.Init(); - m_bUseModNameForIcon = false; - m_vsBroadcastOnExport.clear(); - } - virtual void LoadInternal( const Commands &cmds ) - { - ASSERT( cmds.v.size() == 1 ); - const Command &command = cmds.v[0]; - RString sParam = command.GetArg(1).s; - ASSERT( command.m_vsArgs.size() == 2 ); - ASSERT( sParam.size() != 0 ); - - m_bUseModNameForIcon = true; - - m_Def.m_sName = sParam; - - m_Default.Load( -1, ParseCommands(ENTRY_DEFAULT(sParam)) ); - - { - // Parse the basic configuration metric. - Commands lCmds = ParseCommands( ENTRY(sParam) ); - if( lCmds.v.size() < 1 ) - RageException::Throw( "Parse error in \"ScreenOptionsMaster::%s\".", sParam.c_str() ); - - m_Def.m_bOneChoiceForAllPlayers = false; - const int NumCols = StringToInt( lCmds.v[0].m_vsArgs[0] ); - for( unsigned i=1; i= 0 && pn < NUM_PLAYERS ); - m_Def.m_vEnabledForPlayers.insert( pn ); - } - } - else if( sName == "exportonchange" ) - { - m_Def.m_bExportOnChange = true; - } - else if( sName == "broadcastonexport" ) - { - for( unsigned j=1; jTrace( "\"%s\" is not playable.", sParam.c_str() ); - continue; - } - - m_aListEntries.push_back( mc ); - - RString sName = mc.m_sName; - RString sChoice = mc.m_sName; - m_Def.m_vsChoices.push_back( sChoice ); - } - } - - if( m_Def.m_selectType != SELECT_MULTIPLE && m_Def.m_iDefault == -1 ) - { - for( unsigned e = 0; e < m_aListEntries.size(); ++e ) - { - const GameCommand &mc = m_aListEntries[e]; - if( mc.IsZero() ) - m_Def.m_iDefault = e; - } - } - } - void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const - { - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - vector &vbSelOut = vbSelectedOut[p]; - - bool bUseFallbackOption = true; - - for( unsigned e = 0; e < m_aListEntries.size(); ++e ) - { - const GameCommand &mc = m_aListEntries[e]; - - vbSelOut[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. */ - continue; - } - - if( m_Def.m_bOneChoiceForAllPlayers ) - { - if( mc.DescribesCurrentModeForAllPlayers() ) - { - bUseFallbackOption = false; - if( m_Def.m_selectType != SELECT_MULTIPLE ) - OptionRowHandlerUtil::SelectExactlyOne( e, vbSelOut ); - else - vbSelOut[e] = true; - } - } - else - { - if( mc.DescribesCurrentMode(p) ) - { - bUseFallbackOption = false; - if( m_Def.m_selectType != SELECT_MULTIPLE ) - OptionRowHandlerUtil::SelectExactlyOne( e, vbSelOut ); - else - vbSelOut[e] = true; - } - } - } - - if( m_Def.m_selectType == SELECT_ONE && bUseFallbackOption ) - { - int iFallbackOption = m_Def.m_iDefault; - if( iFallbackOption == -1 ) - { - RString s = ssprintf("No options in row \"list,%s\" were selected, and no fallback row found; selected entry 0", m_Def.m_sName.c_str()); - LOG->Warn( "%s", s.c_str() ); - CHECKPOINT_M( s ); - iFallbackOption = 0; - } - - OptionRowHandlerUtil::SelectExactlyOne( iFallbackOption, vbSelOut ); - } - - VerifySelected( m_Def.m_selectType, vbSelOut, m_Def.m_sName ); - } - } - - int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const - { - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - const vector &vbSel = vbSelected[p]; - - m_Default.Apply( p ); - for( unsigned i=0; iBroadcast( *s ); - return 0; - } - - virtual int GetDefaultOption() const - { - return m_Def.m_iDefault; - } - - virtual void GetIconTextAndGameCommand( int iFirstSelection, RString &sIconTextOut, GameCommand &gcOut ) const - { - sIconTextOut = m_bUseModNameForIcon ? - m_aListEntries[iFirstSelection].m_sPreferredModifiers : - m_Def.m_vsChoices[iFirstSelection]; - - gcOut = m_aListEntries[iFirstSelection]; - } - virtual RString GetScreen( int iChoice ) const - { - const GameCommand &gc = m_aListEntries[iChoice]; - return gc.m_sScreen; - } - - virtual ReloadChanged Reload() - { - // HACK: always reload "speed", to update the BPM text in the name of the speed line - if( !m_Def.m_sName.CompareNoCase("speed") ) - return RELOAD_CHANGED_ALL; - - return OptionRowHandler::Reload(); - } -}; - -static void SortNoteSkins( vector &asSkinNames ) -{ - set setSkinNames; - setSkinNames.insert( asSkinNames.begin(), asSkinNames.end() ); - - vector asSorted; - split( NOTE_SKIN_SORT_ORDER, ",", asSorted ); - - set setUnusedSkinNames( setSkinNames ); - asSkinNames.clear(); - - FOREACH( RString, asSorted, sSkin ) - { - if( setSkinNames.find(*sSkin) == setSkinNames.end() ) - continue; - asSkinNames.push_back( *sSkin ); - setUnusedSkinNames.erase( *sSkin ); - } - - asSkinNames.insert( asSkinNames.end(), setUnusedSkinNames.begin(), setUnusedSkinNames.end() ); -} - -class OptionRowHandlerListNoteSkins : public OptionRowHandlerList -{ - virtual void LoadInternal( const Commands & ) - { - m_Def.m_sName = "NoteSkins"; - m_Def.m_bOneChoiceForAllPlayers = false; - m_Def.m_bAllowThemeItems = false; // we theme the text ourself - - vector arraySkinNames; - NOTESKIN->GetNoteSkinNames( arraySkinNames ); - SortNoteSkins( arraySkinNames ); - - for( unsigned skin=0; skinIsEditing() ) - { - m_Def.m_vsChoices.push_back( "" ); - m_aListEntries.push_back( GameCommand() ); - } - else if( GAMESTATE->IsCourseMode() && GAMESTATE->m_pCurCourse ) // playing a course - { - m_Def.m_bOneChoiceForAllPlayers = (bool)PREFSMAN->m_bLockCourseDifficulties; - m_Def.m_layoutType = StringToLayoutType( STEPS_ROW_LAYOUT_TYPE ); - - vector vTrails; - GAMESTATE->m_pCurCourse->GetTrails( vTrails, GAMESTATE->GetCurrentStyle()->m_StepsType ); - for( unsigned i=0; im_CourseDifficulty ); - s += ssprintf( " %d", pTrail->GetMeter() ); - m_Def.m_vsChoices.push_back( s ); - GameCommand mc; - mc.m_pTrail = pTrail; - m_aListEntries.push_back( mc ); - } - } - else if( GAMESTATE->m_pCurSong ) // playing a song - { - m_Def.m_layoutType = StringToLayoutType( STEPS_ROW_LAYOUT_TYPE ); - - vector vpSteps; - Song *pSong = GAMESTATE->m_pCurSong; - SongUtil::GetSteps( pSong, vpSteps, GAMESTATE->GetCurrentStyle()->m_StepsType ); - StepsUtil::RemoveLockedSteps( pSong, vpSteps ); - StepsUtil::SortNotesArrayByDifficulty( vpSteps ); - for( unsigned i=0; iGetChartName(); - } - // TODO: find a way to make this use lua or metrics. - if (s == "" || s == "blank" || s == "Blank") - { - if( pSteps->GetDifficulty() == Difficulty_Edit ) - { - s = pSteps->GetChartName(); - if (s == "" || s == "blank" || s == "Blank") - s = pSteps->GetDescription(); - } - else - { - if( pSteps->IsAnEdit() ) - { - s = pSteps->GetChartName(); - if (s == "" || s == "blank" || s == "Blank") - s = pSteps->GetDescription(); - } - else - s = CustomDifficultyToLocalizedString( GetCustomDifficulty( pSteps->m_StepsType, pSteps->GetDifficulty(), CourseType_Invalid ) ); - } - } - s += ssprintf( " %d", pSteps->GetMeter() ); - m_Def.m_vsChoices.push_back( s ); - GameCommand mc; - mc.m_pSteps = pSteps; - mc.m_dc = pSteps->GetDifficulty(); - m_aListEntries.push_back( mc ); - } - } - else - { - /* We have neither a song nor a course. We may be preloading the - * screen for future use. */ - m_Def.m_vsChoices.push_back( "n/a" ); - m_aListEntries.push_back( GameCommand() ); - } - - return RELOAD_CHANGED_ALL; - } -}; - -class OptionRowHandlerSteps : public OptionRowHandler -{ -public: - BroadcastOnChangePtr *m_ppStepsToFill; - BroadcastOnChange *m_pDifficultyToFill; - const BroadcastOnChange *m_pst; - vector m_vSteps; - vector m_vDifficulties; - - OptionRowHandlerSteps() { Init(); } - void Init() - { - OptionRowHandler::Init(); - m_ppStepsToFill = NULL; - m_pDifficultyToFill = NULL; - m_vSteps.clear(); - m_vDifficulties.clear(); - } - - virtual void LoadInternal( const Commands &cmds ) - { - ASSERT( cmds.v.size() == 1 ); - const Command &command = cmds.v[0]; - RString sParam = command.GetArg(1).s; - ASSERT( command.m_vsArgs.size() == 2 ); - ASSERT( sParam.size() != 0 ); - - if( sParam == "EditSteps" ) - { - m_ppStepsToFill = &GAMESTATE->m_pCurSteps[0]; - m_pDifficultyToFill = &GAMESTATE->m_PreferredDifficulty[0]; - m_pst = &GAMESTATE->m_stEdit; - m_vsReloadRowMessages.push_back( MessageIDToString(Message_EditStepsTypeChanged) ); - } - else if( sParam == "EditSourceSteps" ) - { - m_ppStepsToFill = &GAMESTATE->m_pEditSourceSteps; - m_pst = &GAMESTATE->m_stEditSource; - m_vsReloadRowMessages.push_back( MessageIDToString(Message_EditSourceStepsTypeChanged) ); - if( GAMESTATE->m_pCurSteps[0].Get() != NULL ) - m_Def.m_vEnabledForPlayers.clear(); // hide row - } - else - { - RageException::Throw( "Invalid StepsType param \"%s\".", sParam.c_str() ); - } - - m_Def.m_sName = sParam; - m_Def.m_bOneChoiceForAllPlayers = true; - m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; - m_Def.m_bExportOnChange = true; - m_Def.m_bAllowThemeItems = false; // we theme the text ourself - m_vsReloadRowMessages.push_back( MessageIDToString(Message_CurrentSongChanged) ); - - m_vDifficulties.clear(); - m_vSteps.clear(); - - if( GAMESTATE->m_pCurSong ) - { - FOREACH_ENUM( Difficulty, dc ) - { - if( dc == Difficulty_Edit ) - continue; - m_vDifficulties.push_back( dc ); - Steps* pSteps = SongUtil::GetStepsByDifficulty( GAMESTATE->m_pCurSong, *m_pst, dc ); - m_vSteps.push_back( pSteps ); - } - SongUtil::GetSteps( GAMESTATE->m_pCurSong, m_vSteps, *m_pst, Difficulty_Edit ); - m_vDifficulties.resize( m_vSteps.size(), Difficulty_Edit ); - - if( sParam == "EditSteps" ) - { - m_vSteps.push_back( NULL ); - m_vDifficulties.push_back( Difficulty_Edit ); - } - - for( unsigned i=0; iGetDescription(); - else - s = "NewEdit"; - } - else - { - s = CustomDifficultyToLocalizedString( GetCustomDifficulty( GAMESTATE->m_stEdit, dc, CourseType_Invalid ) ); - } - m_Def.m_vsChoices.push_back( s ); - } - } - else - { - m_vDifficulties.push_back( Difficulty_Edit ); - m_vSteps.push_back( NULL ); - m_Def.m_vsChoices.push_back( "none" ); - } - - if( m_pDifficultyToFill ) - m_pDifficultyToFill->Set( m_vDifficulties[0] ); - m_ppStepsToFill->Set( m_vSteps[0] ); - } - virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const - { - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - vector &vbSelOut = vbSelectedOut[p]; - - ASSERT( m_vSteps.size() == vbSelOut.size() ); - - // look for matching steps - vector::const_iterator iter = find( m_vSteps.begin(), m_vSteps.end(), m_ppStepsToFill->Get() ); - if( iter != m_vSteps.end() ) - { - unsigned i = iter - m_vSteps.begin(); - vbSelOut[i] = true; - return; - } - // look for matching difficulty - if( m_pDifficultyToFill ) - { - FOREACH_CONST( Difficulty, m_vDifficulties, d ) - { - unsigned i = d - m_vDifficulties.begin(); - if( *d == GAMESTATE->m_PreferredDifficulty[0] ) - { - vbSelOut[i] = true; - vector v; - v.push_back( p ); - ExportOption( v, vbSelectedOut ); // current steps changed - continue; - } - } - } - // default to 1st - vbSelOut[0] = true; - } - } - virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const - { - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - const vector &vbSel = vbSelected[p]; - - int index = OptionRowHandlerUtil::GetOneSelection( vbSel ); - Difficulty dc = m_vDifficulties[index]; - Steps *pSteps = m_vSteps[index]; - if( m_pDifficultyToFill ) - m_pDifficultyToFill->Set( dc ); - m_ppStepsToFill->Set( pSteps ); - } - - return 0; - } -}; - -class OptionRowHandlerListCharacters: public OptionRowHandlerList -{ - virtual void LoadInternal( const Commands & ) - { - m_Def.m_bOneChoiceForAllPlayers = false; - m_Def.m_bAllowThemeItems = false; - m_Def.m_sName = "Characters"; - m_Def.m_iDefault = 0; - m_Default.m_pCharacter = CHARMAN->GetDefaultCharacter(); - - { - m_Def.m_vsChoices.push_back( OFF ); - GameCommand mc; - mc.m_pCharacter = NULL; - m_aListEntries.push_back( mc ); - } - - vector vpCharacters; - CHARMAN->GetCharacters( vpCharacters ); - for( unsigned i=0; iGetDisplayName(); - s.MakeUpper(); - - m_Def.m_vsChoices.push_back( s ); - GameCommand mc; - mc.m_pCharacter = pCharacter; - m_aListEntries.push_back( mc ); - } - } -}; - -class OptionRowHandlerListStyles: public OptionRowHandlerList -{ - virtual void LoadInternal( const Commands & ) - { - m_Def.m_bOneChoiceForAllPlayers = true; - m_Def.m_sName = "Style"; - m_Def.m_bAllowThemeItems = false; // we theme the text ourself - - vector vStyles; - GAMEMAN->GetStylesForGame( GAMESTATE->m_pCurGame, vStyles ); - ASSERT( vStyles.size() != 0 ); - FOREACH_CONST( const Style*, vStyles, s ) - { - m_Def.m_vsChoices.push_back( GAMEMAN->StyleToLocalizedString(*s) ); - GameCommand mc; - mc.m_pStyle = *s; - m_aListEntries.push_back( mc ); - } - - m_Default.m_pStyle = vStyles[0]; - } -}; - -class OptionRowHandlerListGroups: public OptionRowHandlerList -{ - virtual void LoadInternal( const Commands & ) - { - m_Def.m_bOneChoiceForAllPlayers = true; - m_Def.m_bAllowThemeItems = false; // we theme the text ourself - m_Def.m_sName = "Group"; - m_Default.m_sSongGroup = GROUP_ALL; - - vector vSongGroups; - SONGMAN->GetSongGroupNames( vSongGroups ); - ASSERT( vSongGroups.size() != 0 ); - - { - m_Def.m_vsChoices.push_back( "AllGroups" ); - GameCommand mc; - mc.m_sSongGroup = GROUP_ALL; - m_aListEntries.push_back( mc ); - } - - FOREACH_CONST( RString, vSongGroups, g ) - { - m_Def.m_vsChoices.push_back( *g ); - GameCommand mc; - mc.m_sSongGroup = *g; - m_aListEntries.push_back( mc ); - } - } -}; - -class OptionRowHandlerListDifficulties: public OptionRowHandlerList -{ - virtual void LoadInternal( const Commands & ) - { - m_Def.m_bOneChoiceForAllPlayers = true; - m_Def.m_sName = "Difficulty"; - m_Default.m_dc = Difficulty_Invalid; - m_Def.m_bAllowThemeItems = false; // we theme the text ourself - - { - m_Def.m_vsChoices.push_back( "AllDifficulties" ); - GameCommand mc; - mc.m_dc = Difficulty_Invalid; - m_aListEntries.push_back( mc ); - } - - FOREACH_CONST( Difficulty, CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue(), d ) - { - // TODO: Is this the best thing we can do here? - StepsType st = GAMEMAN->GetHowToPlayStyleForGame( GAMESTATE->m_pCurGame )->m_StepsType; - RString s = CustomDifficultyToLocalizedString( GetCustomDifficulty(st, *d, CourseType_Invalid) ); - - m_Def.m_vsChoices.push_back( s ); - GameCommand mc; - mc.m_dc = *d; - m_aListEntries.push_back( mc ); - } - } -}; - -// XXX: very similar to OptionRowHandlerSongChoices -class OptionRowHandlerListSongsInCurrentSongGroup: public OptionRowHandlerList -{ - virtual void LoadInternal( const Commands & ) - { - const vector &vpSongs = SONGMAN->GetSongs( GAMESTATE->m_sPreferredSongGroup ); - - if( GAMESTATE->m_pCurSong == NULL ) - GAMESTATE->m_pCurSong.Set( vpSongs[0] ); - - m_Def.m_sName = "SongsInCurrentSongGroup"; - m_Def.m_bOneChoiceForAllPlayers = true; - m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; - m_Def.m_bExportOnChange = true; - - FOREACH_CONST( Song*, vpSongs, p ) - { - m_Def.m_vsChoices.push_back( (*p)->GetTranslitFullTitle() ); - GameCommand mc; - mc.m_pSong = *p; - m_aListEntries.push_back( mc ); - } - } -}; - -class OptionRowHandlerLua : public OptionRowHandler -{ -public: - LuaReference *m_pLuaTable; - LuaReference m_EnabledForPlayersFunc; - - OptionRowHandlerLua() { m_pLuaTable = new LuaReference; Init(); } - virtual ~OptionRowHandlerLua() { delete m_pLuaTable; } - void Init() - { - OptionRowHandler::Init(); - m_pLuaTable->Unset(); - } - - void SetEnabledForPlayers() - { - Lua *L = LUA->Get(); - - if( m_EnabledForPlayersFunc.IsNil() ) - { - LUA->Release(L); - return; - } - - m_EnabledForPlayersFunc.PushSelf( L ); - - // Argument 1 (self): - m_pLuaTable->PushSelf( L ); - - lua_call( L, 1, 1 ); // call function with 1 argument and 1 result - if( !lua_istable(L, -1) ) - RageException::Throw( "\"EnabledForPlayers\" did not return a table." ); - - m_Def.m_vEnabledForPlayers.clear(); // and fill in with supplied PlayerNumbers below - - lua_pushnil( L ); - while( lua_next(L, -2) != 0 ) - { - // `key' is at index -2 and `value' at index -1 - PlayerNumber pn = (PlayerNumber)luaL_checkint( L, -1 ); - - m_Def.m_vEnabledForPlayers.insert( pn ); - - lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration - } - lua_pop( L, 1 ); - - LUA->Release(L); - } - - virtual void LoadInternal( const Commands &cmds ) - { - ASSERT( cmds.v.size() == 1 ); - const Command &command = cmds.v[0]; - ASSERT( command.m_vsArgs.size() == 2 ); - RString sLuaFunction = command.m_vsArgs[1]; - ASSERT( sLuaFunction.size() != 0 ); - - m_Def.m_bAllowThemeItems = false; // Lua options are always dynamic and can theme themselves. - - Lua *L = LUA->Get(); - - // Run the Lua expression. It should return a table. - m_pLuaTable->SetFromExpression( sLuaFunction ); - - if( m_pLuaTable->GetLuaType() != LUA_TTABLE ) - RageException::Throw( "Result of \"%s\" is not a table.", sLuaFunction.c_str() ); - - m_pLuaTable->PushSelf( L ); - - lua_pushstring( L, "Name" ); - lua_gettable( L, -2 ); - const char *pStr = lua_tostring( L, -1 ); - if( pStr == NULL ) - RageException::Throw( "\"%s\" \"Name\" entry is not a string.", sLuaFunction.c_str() ); - m_Def.m_sName = pStr; - lua_pop( L, 1 ); - - lua_pushstring( L, "OneChoiceForAllPlayers" ); - lua_gettable( L, -2 ); - m_Def.m_bOneChoiceForAllPlayers = !!lua_toboolean( L, -1 ); - lua_pop( L, 1 ); - - lua_pushstring( L, "ExportOnChange" ); - lua_gettable( L, -2 ); - m_Def.m_bExportOnChange = !!lua_toboolean( L, -1 ); - lua_pop( L, 1 ); - - lua_pushstring( L, "LayoutType" ); - lua_gettable( L, -2 ); - pStr = lua_tostring( L, -1 ); - if( pStr == NULL ) - RageException::Throw( "\"%s\" \"LayoutType\" entry is not a string.", sLuaFunction.c_str() ); - m_Def.m_layoutType = StringToLayoutType( pStr ); - ASSERT( m_Def.m_layoutType != LayoutType_Invalid ); - lua_pop( L, 1 ); - - lua_pushstring( L, "SelectType" ); - lua_gettable( L, -2 ); - pStr = lua_tostring( L, -1 ); - if( pStr == NULL ) - RageException::Throw( "\"%s\" \"SelectType\" entry is not a string.", sLuaFunction.c_str() ); - m_Def.m_selectType = StringToSelectType( pStr ); - ASSERT( m_Def.m_selectType != SelectType_Invalid ); - lua_pop( L, 1 ); - - // Iterate over the "Choices" table. - lua_pushstring( L, "Choices" ); - lua_gettable( L, -2 ); - if( !lua_istable( L, -1 ) ) - RageException::Throw( "\"%s\" \"Choices\" is not a table.", sLuaFunction.c_str() ); - - lua_pushnil( L ); - while( lua_next(L, -2) != 0 ) - { - // `key' is at index -2 and `value' at index -1 - const char *pValue = lua_tostring( L, -1 ); - if( pValue == NULL ) - RageException::Throw( "\"%s\" Column entry is not a string.", sLuaFunction.c_str() ); -// LOG->Trace( "'%s'", pValue); - - m_Def.m_vsChoices.push_back( pValue ); - - lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration - } - - lua_pop( L, 1 ); // pop choices table - - // Set the EnabledForPlayers function. - lua_pushstring( L, "EnabledForPlayers" ); - lua_gettable( L, -2 ); - if( !lua_isfunction( L, -1 ) && !lua_isnil( L, -1 ) ) - RageException::Throw( "\"%s\" \"EnabledForPlayers\" is not a table.", sLuaFunction.c_str() ); - m_EnabledForPlayersFunc.SetFromStack( L ); - SetEnabledForPlayers(); - - // Iterate over the "ReloadRowMessages" table. - lua_pushstring( L, "ReloadRowMessages" ); - lua_gettable( L, -2 ); - if( !lua_isnil( L, -1 ) ) - { - if( !lua_istable( L, -1 ) ) - RageException::Throw( "\"%s\" \"ReloadRowMessages\" is not a table.", sLuaFunction.c_str() ); - - lua_pushnil( L ); - while( lua_next(L, -2) != 0 ) - { - // `key' is at index -2 and `value' at index -1 - const char *pValue = lua_tostring( L, -1 ); - if( pValue == NULL ) - RageException::Throw( "\"%s\" Column entry is not a string.", sLuaFunction.c_str() ); - LOG->Trace( "Found ReloadRowMessage '%s'", pValue); - - m_vsReloadRowMessages.push_back( pValue ); - - lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration - } - } - lua_pop( L, 1 ); // pop ReloadRowMessages table - - // Look for "ExportOnChange" value. - lua_pushstring( L, "ExportOnChange" ); - lua_gettable( L, -2 ); - if( !lua_isnil( L, -1 ) ) - { - m_Def.m_bExportOnChange = !!MyLua_checkboolean( L, -1 ); - } - lua_pop( L, 1 ); // pop ExportOnChange value - - lua_pop( L, 1 ); // pop main table - ASSERT( lua_gettop(L) == 0 ); - - LUA->Release(L); - } - - virtual ReloadChanged Reload() - { - SetEnabledForPlayers(); - return RELOAD_CHANGED_ENABLED; - } - - virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const - { - Lua *L = LUA->Get(); - - ASSERT( lua_gettop(L) == 0 ); - - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - vector &vbSelOut = vbSelectedOut[p]; - - /* Evaluate the LoadSelections(self,array,pn) function, where - * array is a table representing vbSelectedOut. */ - - // All selections default to false. - for( unsigned i = 0; i < vbSelOut.size(); ++i ) - vbSelOut[i] = false; - - // Create the vbSelectedOut table - LuaHelpers::CreateTableFromArrayB( L, vbSelOut ); - ASSERT( lua_gettop(L) == 1 ); // vbSelectedOut table - - // Get the function to call from m_LuaTable. - m_pLuaTable->PushSelf( L ); - ASSERT( lua_istable( L, -1 ) ); - - lua_pushstring( L, "LoadSelections" ); - lua_gettable( L, -2 ); - if( !lua_isfunction( L, -1 ) ) - RageException::Throw( "\"%s\" \"LoadSelections\" entry is not a function.", m_Def.m_sName.c_str() ); - - // Argument 1 (self): - m_pLuaTable->PushSelf( L ); - - // Argument 2 (vbSelectedOut): - lua_pushvalue( L, 1 ); - - // Argument 3 (pn): - LuaHelpers::Push( L, p ); - - ASSERT( lua_gettop(L) == 6 ); // vbSelectedOut, m_iLuaTable, function, self, arg, arg - - lua_call( L, 3, 0 ); // call function with 3 arguments and 0 results - ASSERT( lua_gettop(L) == 2 ); - - lua_pop( L, 1 ); // pop option table - - LuaHelpers::ReadArrayFromTableB( L, vbSelOut ); - - lua_pop( L, 1 ); // pop vbSelectedOut table - - ASSERT( lua_gettop(L) == 0 ); - } - - LUA->Release(L); - } - virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const - { - Lua *L = LUA->Get(); - - ASSERT( lua_gettop(L) == 0 ); - - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - const vector &vbSel = vbSelected[p]; - - /* Evaluate SaveSelections(self,array,pn) function, where array is - * a table representing vbSelectedOut. */ - - vector vbSelectedCopy = vbSel; - - // Create the vbSelectedOut table. - LuaHelpers::CreateTableFromArrayB( L, vbSelectedCopy ); - ASSERT( lua_gettop(L) == 1 ); // vbSelectedOut table - - // Get the function to call. - m_pLuaTable->PushSelf( L ); - ASSERT( lua_istable( L, -1 ) ); - - lua_pushstring( L, "SaveSelections" ); - lua_gettable( L, -2 ); - if( !lua_isfunction( L, -1 ) ) - RageException::Throw( "\"%s\" \"SaveSelections\" entry is not a function.", m_Def.m_sName.c_str() ); - - // Argument 1 (self): - m_pLuaTable->PushSelf( L ); - - // Argument 2 (vbSelectedOut): - lua_pushvalue( L, 1 ); - - // Argument 3 (pn): - LuaHelpers::Push( L, p ); - - ASSERT( lua_gettop(L) == 6 ); // vbSelectedOut, m_iLuaTable, function, self, arg, arg - - lua_call( L, 3, 0 ); // call function with 3 arguments and 0 results - ASSERT( lua_gettop(L) == 2 ); - - lua_pop( L, 1 ); // pop option table - lua_pop( L, 1 ); // pop vbSelected table - - ASSERT( lua_gettop(L) == 0 ); - } - - LUA->Release(L); - - // XXX: allow specifying the mask - return 0; - } -}; - -class OptionRowHandlerConfig : public OptionRowHandler -{ -public: - const ConfOption *m_pOpt; - - OptionRowHandlerConfig() { Init(); } - void Init() - { - OptionRowHandler::Init(); - m_pOpt = NULL; - } - virtual void LoadInternal( const Commands &cmds ) - { - ASSERT( cmds.v.size() == 1 ); - const Command &command = cmds.v[0]; - RString sParam = command.GetArg(1).s; - ASSERT( command.m_vsArgs.size() == 2 ); - ASSERT( sParam.size() != 0 ); - - Init(); - - // Configuration values are never per-player. - m_Def.m_bOneChoiceForAllPlayers = true; - - ConfOption *pConfOption = ConfOption::Find( sParam ); - if( pConfOption == NULL ) - { - LOG->Warn( "Invalid Conf type \"%s\"", sParam.c_str() ); - pConfOption = ConfOption::Find( "Invalid" ); - ASSERT_M( pConfOption != NULL, "ConfOption::Find(Invalid)" ); - } - - pConfOption->UpdateAvailableOptions(); - - m_pOpt = pConfOption; - m_pOpt->MakeOptionsList( m_Def.m_vsChoices ); - - m_Def.m_bAllowThemeItems = m_pOpt->m_bAllowThemeItems; - - m_Def.m_sName = m_pOpt->name; - } - virtual void ImportOption( OptionRow *, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const - { - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - vector &vbSelOut = vbSelectedOut[p]; - - int iSelection = m_pOpt->Get(); - OptionRowHandlerUtil::SelectExactlyOne( iSelection, vbSelOut ); - } - } - virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const - { - bool bChanged = false; - - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - const vector &vbSel = vbSelected[p]; - - int iSel = OptionRowHandlerUtil::GetOneSelection(vbSel); - - // Get the original choice. - int iOriginal = m_pOpt->Get(); - - // Apply. - m_pOpt->Put( iSel ); - - // Get the new choice. - int iNew = m_pOpt->Get(); - - // If it didn't change, don't return any side-effects. - if( iOriginal != iNew ) - bChanged = true; - } - - return bChanged ? m_pOpt->GetEffects() : 0; - } -}; - -class OptionRowHandlerStepsType : public OptionRowHandler -{ -public: - BroadcastOnChange *m_pstToFill; - vector m_vStepsTypesToShow; - - OptionRowHandlerStepsType() { Init(); } - void Init() - { - OptionRowHandler::Init(); - m_pstToFill = NULL; - m_vStepsTypesToShow.clear(); - } - - virtual void LoadInternal( const Commands &cmds ) - { - ASSERT( cmds.v.size() == 1 ); - const Command &command = cmds.v[0]; - RString sParam = command.GetArg(1).s; - ASSERT( command.m_vsArgs.size() == 2 ); - ASSERT( sParam.size() != 0 ); - - if( sParam == "EditStepsType" ) - { - m_pstToFill = &GAMESTATE->m_stEdit; - } - else if( sParam == "EditSourceStepsType" ) - { - m_pstToFill = &GAMESTATE->m_stEditSource; - m_vsReloadRowMessages.push_back( MessageIDToString(Message_CurrentStepsP1Changed) ); - m_vsReloadRowMessages.push_back( MessageIDToString(Message_EditStepsTypeChanged) ); - if( GAMESTATE->m_pCurSteps[0].Get() != NULL ) - m_Def.m_vEnabledForPlayers.clear(); // hide row - } - else - { - RageException::Throw( "Invalid StepsType param \"%s\".", sParam.c_str() ); - } - - m_Def.m_sName = sParam; - m_Def.m_bOneChoiceForAllPlayers = true; - m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; - m_Def.m_bExportOnChange = true; - m_Def.m_bAllowThemeItems = false; // we theme the text ourself - - // calculate which StepsTypes to show - m_vStepsTypesToShow = CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue(); - - m_Def.m_vsChoices.clear(); - FOREACH_CONST( StepsType, m_vStepsTypesToShow, st ) - { - RString s = GAMEMAN->GetStepsTypeInfo( *st ).GetLocalizedString(); - m_Def.m_vsChoices.push_back( s ); - } - - if( *m_pstToFill == StepsType_Invalid ) - m_pstToFill->Set( m_vStepsTypesToShow[0] ); - } - - virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const - { - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - vector &vbSelOut = vbSelectedOut[p]; - - if( GAMESTATE->m_pCurSteps[0] ) - { - StepsType st = GAMESTATE->m_pCurSteps[0]->m_StepsType; - vector::const_iterator iter = find( m_vStepsTypesToShow.begin(), m_vStepsTypesToShow.end(), st ); - if( iter != m_vStepsTypesToShow.end() ) - { - unsigned i = iter - m_vStepsTypesToShow.begin(); - vbSelOut[i] = true; - continue; // done with this player - } - } - vbSelOut[0] = true; - } - } - virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const - { - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - PlayerNumber p = *pn; - const vector &vbSel = vbSelected[p]; - - int index = OptionRowHandlerUtil::GetOneSelection( vbSel ); - m_pstToFill->Set( m_vStepsTypesToShow[index] ); - } - - return 0; - } -}; - - -class OptionRowHandlerGameCommand : public OptionRowHandler -{ -public: - GameCommand m_gc; - - OptionRowHandlerGameCommand() { Init(); } - void Init() - { - OptionRowHandler::Init(); - m_gc.Init(); - m_gc.ApplyCommitsScreens( false ); - } - virtual void LoadInternal( const Commands &cmds ) - { - ASSERT( cmds.v.size() > 1 ); - - Commands temp = cmds; - temp.v.erase( temp.v.begin() ); - m_gc.Load( 0, temp ); - ASSERT( !m_gc.m_sName.empty() ); - m_Def.m_sName = m_gc.m_sName; - m_Def.m_bOneChoiceForAllPlayers = true; - m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; - m_Def.m_selectType = SELECT_NONE; - m_Def.m_vsChoices.push_back( "" ); - } - virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const - { - } - virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const - { - if( vbSelected[PLAYER_1][0] || vbSelected[PLAYER_2][0] ) - m_gc.ApplyToAllPlayers(); - return 0; - } - virtual void GetIconTextAndGameCommand( int iFirstSelection, RString &sIconTextOut, GameCommand &gcOut ) const - { - sIconTextOut = ""; - gcOut = m_gc; - } - virtual RString GetScreen( int iChoice ) const - { - return m_gc.m_sScreen; - } -}; - -class OptionRowHandlerNull: public OptionRowHandler -{ -public: - OptionRowHandlerNull() { Init(); } -}; - -/////////////////////////////////////////////////////////////////////////////////// - -OptionRowHandler* OptionRowHandlerUtil::Make( const Commands &cmds ) -{ - OptionRowHandler* pHand = NULL; - - if( cmds.v.size() == 0 ) - return NULL; - - const RString &name = cmds.v[0].GetName(); - -#define MAKE( type ) { type *p = new type; p->Load( cmds ); pHand = p; } - - // XXX: merge these, and merge "Steps" and "list,Steps" - if( name == "list" ) - { - const Command &command = cmds.v[0]; - RString sParam = command.GetArg(1).s; - if( command.m_vsArgs.size() != 2 || !sParam.size() ) - return NULL; - - if( sParam.CompareNoCase("NoteSkins")==0 ) MAKE( OptionRowHandlerListNoteSkins ) - else if( sParam.CompareNoCase("Steps")==0 ) MAKE( OptionRowHandlerListSteps ) - else if( sParam.CompareNoCase("StepsLocked")==0 ) - { - MAKE( OptionRowHandlerListSteps ); - pHand->m_Def.m_bOneChoiceForAllPlayers = true; - } - else if( sParam.CompareNoCase("Characters")==0 ) MAKE( OptionRowHandlerListCharacters ) - else if( sParam.CompareNoCase("Styles")==0 ) MAKE( OptionRowHandlerListStyles ) - else if( sParam.CompareNoCase("Groups")==0 ) MAKE( OptionRowHandlerListGroups ) - else if( sParam.CompareNoCase("Difficulties")==0 ) MAKE( OptionRowHandlerListDifficulties ) - else if( sParam.CompareNoCase("SongsInCurrentSongGroup")==0 ) MAKE( OptionRowHandlerListSongsInCurrentSongGroup ) - else MAKE( OptionRowHandlerList ) - } - else if( name == "lua" ) MAKE( OptionRowHandlerLua ) - else if( name == "conf" ) MAKE( OptionRowHandlerConfig ) - else if( name == "stepstype" ) MAKE( OptionRowHandlerStepsType ) - else if( name == "steps" ) MAKE( OptionRowHandlerSteps ) - else if( name == "gamecommand" ) MAKE( OptionRowHandlerGameCommand ) - - return pHand; -} - -OptionRowHandler* OptionRowHandlerUtil::MakeNull() -{ - OptionRowHandler* pHand = NULL; - Commands cmds; - MAKE( OptionRowHandlerNull ) - return pHand; -} - -OptionRowHandler* OptionRowHandlerUtil::MakeSimple( const MenuRowDef &mr ) -{ - OptionRowHandler *pHand = OptionRowHandlerUtil::MakeNull(); - - pHand->m_Def.m_sName = mr.sName; - FontCharAliases::ReplaceMarkers( pHand->m_Def.m_sName ); // Allow special characters - - pHand->m_Def.m_vEnabledForPlayers.clear(); - if( mr.pfnEnabled? mr.pfnEnabled():mr.bEnabled ) - { - FOREACH_EnabledPlayer( pn ) - pHand->m_Def.m_vEnabledForPlayers.insert( pn ); - } - - pHand->m_Def.m_bOneChoiceForAllPlayers = true; - pHand->m_Def.m_selectType = SELECT_ONE; - pHand->m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; - pHand->m_Def.m_bExportOnChange = false;//true; - - // MISTER CHOICES! - pHand->m_Def.m_vsChoices = mr.choices; - - // Each row must have at least one choice. - if( pHand->m_Def.m_vsChoices.empty() ) - pHand->m_Def.m_vsChoices.push_back( "" ); - - pHand->m_Def.m_bAllowThemeTitle = mr.bThemeTitle; - pHand->m_Def.m_bAllowThemeItems = mr.bThemeItems; - - FOREACH( RString, pHand->m_Def.m_vsChoices, c ) - FontCharAliases::ReplaceMarkers( *c ); // Allow special characters - - return pHand; -} - -/* - * (c) 2002-2004 Chris Danford - * All rights reserved. - * - * 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. - * - * 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. - */ +#include "global.h" +#include "OptionRowHandler.h" +#include "LuaManager.h" +#include "ScreenOptionsMasterPrefs.h" +#include "NoteSkinManager.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "GameState.h" +#include "Course.h" +#include "Steps.h" +#include "Style.h" +#include "Song.h" +#include "SongManager.h" +#include "Character.h" +#include "PrefsManager.h" +#include "SongUtil.h" +#include "StepsUtil.h" +#include "GameManager.h" +#include "Foreach.h" +#include "GameSoundManager.h" +#include "CommonMetrics.h" +#include "CharacterManager.h" +#include "ScreenManager.h" +#include "ScreenMiniMenu.h" // for MenuRowDef +#include "FontCharAliases.h" + +#define ENTRY(s) THEME->GetMetric ("ScreenOptionsMaster",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 NOTE_SKIN_SORT_ORDER THEME->GetMetric ("ScreenOptionsMaster","NoteSkinSortOrder") +#define STEPS_ROW_LAYOUT_TYPE THEME->GetMetric("ScreenOptionsMaster","StepsRowLayoutType") +#define STEPS_USE_CHART_NAME THEME->GetMetricB("ScreenOptionsMaster","StepsUseChartName") + +static const char *SelectTypeNames[] = { + "SelectOne", + "SelectMultiple", + "SelectNone", +}; +XToString( SelectType ); +StringToX( SelectType ); +LuaXType( SelectType ); + +static const char *LayoutTypeNames[] = { + "ShowAllInRow", + "ShowOneInRow", +}; +XToString( LayoutType ); +StringToX( LayoutType ); +LuaXType( LayoutType ); + +RString OptionRowHandler::OptionTitle() const +{ + bool bTheme = false; + + // HACK: Always theme the NEXT_ROW and EXIT items, even if metrics says not to theme. + if( m_Def.m_bAllowThemeTitle ) + bTheme = true; + + RString s = m_Def.m_sName; + if( s.empty() ) + return s; + + return bTheme ? THEME->GetString("OptionTitles",s) : s; +} + +RString OptionRowHandler::GetThemedItemText( int iChoice ) const +{ + RString s = m_Def.m_vsChoices[iChoice]; + if( s == "" ) + return ""; + bool bTheme = false; + + if( m_Def.m_bAllowThemeItems ) bTheme = true; + + // Items beginning with a pipe mean "don't theme". + // This allows us to disable theming on a per-choice basis for choice names that are just a number + // and don't need to be localized. + if( s[0] == '|' ) + { + s.erase( s.begin() ); + bTheme = false; + } + + if( bTheme ) + s = CommonMetrics::LocalizeOptionItem( s, false ); + return s; +} + +void OptionRowHandler::GetIconTextAndGameCommand( int iFirstSelection, RString &sIconTextOut, GameCommand &gcOut ) const +{ + sIconTextOut = ""; + gcOut.Init(); +} + +void OptionRowHandlerUtil::SelectExactlyOne( int iSelection, vector &vbSelectedOut ) +{ + ASSERT_M( iSelection >= 0 && iSelection < (int) vbSelectedOut.size(), + ssprintf("%d/%u",iSelection, unsigned(vbSelectedOut.size())) ); + for( int i=0; i &vbSelected ) +{ + int iRet = -1; + for( unsigned i=0; i m_aListEntries; + GameCommand m_Default; + bool m_bUseModNameForIcon; + vector m_vsBroadcastOnExport; + + OptionRowHandlerList() { Init(); } + virtual void Init() + { + OptionRowHandler::Init(); + m_aListEntries.clear(); + m_Default.Init(); + m_bUseModNameForIcon = false; + m_vsBroadcastOnExport.clear(); + } + virtual void LoadInternal( const Commands &cmds ) + { + ASSERT( cmds.v.size() == 1 ); + const Command &command = cmds.v[0]; + RString sParam = command.GetArg(1).s; + ASSERT( command.m_vsArgs.size() == 2 ); + ASSERT( sParam.size() != 0 ); + + m_bUseModNameForIcon = true; + + m_Def.m_sName = sParam; + + m_Default.Load( -1, ParseCommands(ENTRY_DEFAULT(sParam)) ); + + { + // Parse the basic configuration metric. + Commands lCmds = ParseCommands( ENTRY(sParam) ); + if( lCmds.v.size() < 1 ) + RageException::Throw( "Parse error in \"ScreenOptionsMaster::%s\".", sParam.c_str() ); + + m_Def.m_bOneChoiceForAllPlayers = false; + const int NumCols = StringToInt( lCmds.v[0].m_vsArgs[0] ); + for( unsigned i=1; i= 0 && pn < NUM_PLAYERS ); + m_Def.m_vEnabledForPlayers.insert( pn ); + } + } + else if( sName == "exportonchange" ) + { + m_Def.m_bExportOnChange = true; + } + else if( sName == "broadcastonexport" ) + { + for( unsigned j=1; jTrace( "\"%s\" is not playable.", sParam.c_str() ); + continue; + } + + m_aListEntries.push_back( mc ); + + RString sName = mc.m_sName; + RString sChoice = mc.m_sName; + m_Def.m_vsChoices.push_back( sChoice ); + } + } + + if( m_Def.m_selectType != SELECT_MULTIPLE && m_Def.m_iDefault == -1 ) + { + for( unsigned e = 0; e < m_aListEntries.size(); ++e ) + { + const GameCommand &mc = m_aListEntries[e]; + if( mc.IsZero() ) + m_Def.m_iDefault = e; + } + } + } + void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const + { + for (PlayerNumber const &p : vpns) + { + vector &vbSelOut = vbSelectedOut[p]; + + bool bUseFallbackOption = true; + + for( unsigned e = 0; e < m_aListEntries.size(); ++e ) + { + const GameCommand &mc = m_aListEntries[e]; + + vbSelOut[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. */ + continue; + } + + if( m_Def.m_bOneChoiceForAllPlayers ) + { + if( mc.DescribesCurrentModeForAllPlayers() ) + { + bUseFallbackOption = false; + if( m_Def.m_selectType != SELECT_MULTIPLE ) + OptionRowHandlerUtil::SelectExactlyOne( e, vbSelOut ); + else + vbSelOut[e] = true; + } + } + else + { + if( mc.DescribesCurrentMode(p) ) + { + bUseFallbackOption = false; + if( m_Def.m_selectType != SELECT_MULTIPLE ) + OptionRowHandlerUtil::SelectExactlyOne( e, vbSelOut ); + else + vbSelOut[e] = true; + } + } + } + + if( m_Def.m_selectType == SELECT_ONE && bUseFallbackOption ) + { + int iFallbackOption = m_Def.m_iDefault; + if( iFallbackOption == -1 ) + { + RString s = ssprintf("No options in row \"list,%s\" were selected, and no fallback row found; selected entry 0", m_Def.m_sName.c_str()); + LOG->Warn( "%s", s.c_str() ); + CHECKPOINT_M( s ); + iFallbackOption = 0; + } + + OptionRowHandlerUtil::SelectExactlyOne( iFallbackOption, vbSelOut ); + } + + VerifySelected( m_Def.m_selectType, vbSelOut, m_Def.m_sName ); + } + } + + int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const + { + for (PlayerNumber const &p : vpns) + { + const vector &vbSel = vbSelected[p]; + + m_Default.Apply( p ); + for( unsigned i=0; iBroadcast( s ); + return 0; + } + + virtual int GetDefaultOption() const + { + return m_Def.m_iDefault; + } + + virtual void GetIconTextAndGameCommand( int iFirstSelection, RString &sIconTextOut, GameCommand &gcOut ) const + { + sIconTextOut = m_bUseModNameForIcon ? + m_aListEntries[iFirstSelection].m_sPreferredModifiers : + m_Def.m_vsChoices[iFirstSelection]; + + gcOut = m_aListEntries[iFirstSelection]; + } + virtual RString GetScreen( int iChoice ) const + { + const GameCommand &gc = m_aListEntries[iChoice]; + return gc.m_sScreen; + } + + virtual ReloadChanged Reload() + { + // HACK: always reload "speed", to update the BPM text in the name of the speed line + if( !m_Def.m_sName.CompareNoCase("speed") ) + return RELOAD_CHANGED_ALL; + + return OptionRowHandler::Reload(); + } +}; + +static void SortNoteSkins( vector &asSkinNames ) +{ + set setSkinNames; + setSkinNames.insert( asSkinNames.begin(), asSkinNames.end() ); + + vector asSorted; + split( NOTE_SKIN_SORT_ORDER, ",", asSorted ); + + set setUnusedSkinNames( setSkinNames ); + asSkinNames.clear(); + + FOREACH( RString, asSorted, sSkin ) + { + if( setSkinNames.find(*sSkin) == setSkinNames.end() ) + continue; + asSkinNames.push_back( *sSkin ); + setUnusedSkinNames.erase( *sSkin ); + } + + asSkinNames.insert( asSkinNames.end(), setUnusedSkinNames.begin(), setUnusedSkinNames.end() ); +} + +class OptionRowHandlerListNoteSkins : public OptionRowHandlerList +{ + virtual void LoadInternal( const Commands & ) + { + m_Def.m_sName = "NoteSkins"; + m_Def.m_bOneChoiceForAllPlayers = false; + m_Def.m_bAllowThemeItems = false; // we theme the text ourself + + vector arraySkinNames; + NOTESKIN->GetNoteSkinNames( arraySkinNames ); + SortNoteSkins( arraySkinNames ); + + for( unsigned skin=0; skinIsEditing() ) + { + m_Def.m_vsChoices.push_back( "" ); + m_aListEntries.push_back( GameCommand() ); + } + else if( GAMESTATE->IsCourseMode() && GAMESTATE->m_pCurCourse ) // playing a course + { + m_Def.m_bOneChoiceForAllPlayers = (bool)PREFSMAN->m_bLockCourseDifficulties; + m_Def.m_layoutType = StringToLayoutType( STEPS_ROW_LAYOUT_TYPE ); + + vector vTrails; + GAMESTATE->m_pCurCourse->GetTrails( vTrails, GAMESTATE->GetCurrentStyle()->m_StepsType ); + for( unsigned i=0; im_CourseDifficulty ); + s += ssprintf( " %d", pTrail->GetMeter() ); + m_Def.m_vsChoices.push_back( s ); + GameCommand mc; + mc.m_pTrail = pTrail; + m_aListEntries.push_back( mc ); + } + } + else if( GAMESTATE->m_pCurSong ) // playing a song + { + m_Def.m_layoutType = StringToLayoutType( STEPS_ROW_LAYOUT_TYPE ); + + vector vpSteps; + Song *pSong = GAMESTATE->m_pCurSong; + SongUtil::GetSteps( pSong, vpSteps, GAMESTATE->GetCurrentStyle()->m_StepsType ); + StepsUtil::RemoveLockedSteps( pSong, vpSteps ); + StepsUtil::SortNotesArrayByDifficulty( vpSteps ); + for( unsigned i=0; iGetChartName(); + } + // TODO: find a way to make this use lua or metrics. + if (s == "" || s == "blank" || s == "Blank") + { + if( pSteps->GetDifficulty() == Difficulty_Edit ) + { + s = pSteps->GetChartName(); + if (s == "" || s == "blank" || s == "Blank") + s = pSteps->GetDescription(); + } + else + { + if( pSteps->IsAnEdit() ) + { + s = pSteps->GetChartName(); + if (s == "" || s == "blank" || s == "Blank") + s = pSteps->GetDescription(); + } + else + s = CustomDifficultyToLocalizedString( GetCustomDifficulty( pSteps->m_StepsType, pSteps->GetDifficulty(), CourseType_Invalid ) ); + } + } + s += ssprintf( " %d", pSteps->GetMeter() ); + m_Def.m_vsChoices.push_back( s ); + GameCommand mc; + mc.m_pSteps = pSteps; + mc.m_dc = pSteps->GetDifficulty(); + m_aListEntries.push_back( mc ); + } + } + else + { + /* We have neither a song nor a course. We may be preloading the + * screen for future use. */ + m_Def.m_vsChoices.push_back( "n/a" ); + m_aListEntries.push_back( GameCommand() ); + } + + return RELOAD_CHANGED_ALL; + } +}; + +class OptionRowHandlerSteps : public OptionRowHandler +{ +public: + BroadcastOnChangePtr *m_ppStepsToFill; + BroadcastOnChange *m_pDifficultyToFill; + const BroadcastOnChange *m_pst; + vector m_vSteps; + vector m_vDifficulties; + + OptionRowHandlerSteps() { Init(); } + void Init() + { + OptionRowHandler::Init(); + m_ppStepsToFill = NULL; + m_pDifficultyToFill = NULL; + m_vSteps.clear(); + m_vDifficulties.clear(); + } + + virtual void LoadInternal( const Commands &cmds ) + { + ASSERT( cmds.v.size() == 1 ); + const Command &command = cmds.v[0]; + RString sParam = command.GetArg(1).s; + ASSERT( command.m_vsArgs.size() == 2 ); + ASSERT( sParam.size() != 0 ); + + if( sParam == "EditSteps" ) + { + m_ppStepsToFill = &GAMESTATE->m_pCurSteps[0]; + m_pDifficultyToFill = &GAMESTATE->m_PreferredDifficulty[0]; + m_pst = &GAMESTATE->m_stEdit; + m_vsReloadRowMessages.push_back( MessageIDToString(Message_EditStepsTypeChanged) ); + } + else if( sParam == "EditSourceSteps" ) + { + m_ppStepsToFill = &GAMESTATE->m_pEditSourceSteps; + m_pst = &GAMESTATE->m_stEditSource; + m_vsReloadRowMessages.push_back( MessageIDToString(Message_EditSourceStepsTypeChanged) ); + if( GAMESTATE->m_pCurSteps[0].Get() != NULL ) + m_Def.m_vEnabledForPlayers.clear(); // hide row + } + else + { + RageException::Throw( "Invalid StepsType param \"%s\".", sParam.c_str() ); + } + + m_Def.m_sName = sParam; + m_Def.m_bOneChoiceForAllPlayers = true; + m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; + m_Def.m_bExportOnChange = true; + m_Def.m_bAllowThemeItems = false; // we theme the text ourself + m_vsReloadRowMessages.push_back( MessageIDToString(Message_CurrentSongChanged) ); + + m_vDifficulties.clear(); + m_vSteps.clear(); + + if( GAMESTATE->m_pCurSong ) + { + FOREACH_ENUM( Difficulty, dc ) + { + if( dc == Difficulty_Edit ) + continue; + m_vDifficulties.push_back( dc ); + Steps* pSteps = SongUtil::GetStepsByDifficulty( GAMESTATE->m_pCurSong, *m_pst, dc ); + m_vSteps.push_back( pSteps ); + } + SongUtil::GetSteps( GAMESTATE->m_pCurSong, m_vSteps, *m_pst, Difficulty_Edit ); + m_vDifficulties.resize( m_vSteps.size(), Difficulty_Edit ); + + if( sParam == "EditSteps" ) + { + m_vSteps.push_back( NULL ); + m_vDifficulties.push_back( Difficulty_Edit ); + } + + for( unsigned i=0; iGetDescription(); + else + s = "NewEdit"; + } + else + { + s = CustomDifficultyToLocalizedString( GetCustomDifficulty( GAMESTATE->m_stEdit, dc, CourseType_Invalid ) ); + } + m_Def.m_vsChoices.push_back( s ); + } + } + else + { + m_vDifficulties.push_back( Difficulty_Edit ); + m_vSteps.push_back( NULL ); + m_Def.m_vsChoices.push_back( "none" ); + } + + if( m_pDifficultyToFill ) + m_pDifficultyToFill->Set( m_vDifficulties[0] ); + m_ppStepsToFill->Set( m_vSteps[0] ); + } + virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const + { + for (PlayerNumber const &p : vpns) + { + vector &vbSelOut = vbSelectedOut[p]; + + ASSERT( m_vSteps.size() == vbSelOut.size() ); + + // look for matching steps + vector::const_iterator iter = find( m_vSteps.begin(), m_vSteps.end(), m_ppStepsToFill->Get() ); + if( iter != m_vSteps.end() ) + { + unsigned i = iter - m_vSteps.begin(); + vbSelOut[i] = true; + return; + } + // look for matching difficulty + if( m_pDifficultyToFill ) + { + // use the old style for now. + for (vector::const_iterator d = m_vDifficulties.begin(); d != m_vDifficulties.end(); ++d) + { + unsigned i = d - m_vDifficulties.begin(); + if( *d == GAMESTATE->m_PreferredDifficulty[0] ) + { + vbSelOut[i] = true; + vector v; + v.push_back( p ); + ExportOption( v, vbSelectedOut ); // current steps changed + continue; + } + } + } + // default to 1st + vbSelOut[0] = true; + } + } + virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const + { + for (PlayerNumber const &p : vpns) + { + const vector &vbSel = vbSelected[p]; + + int index = OptionRowHandlerUtil::GetOneSelection( vbSel ); + Difficulty dc = m_vDifficulties[index]; + Steps *pSteps = m_vSteps[index]; + if( m_pDifficultyToFill ) + m_pDifficultyToFill->Set( dc ); + m_ppStepsToFill->Set( pSteps ); + } + + return 0; + } +}; + +class OptionRowHandlerListCharacters: public OptionRowHandlerList +{ + virtual void LoadInternal( const Commands & ) + { + m_Def.m_bOneChoiceForAllPlayers = false; + m_Def.m_bAllowThemeItems = false; + m_Def.m_sName = "Characters"; + m_Def.m_iDefault = 0; + m_Default.m_pCharacter = CHARMAN->GetDefaultCharacter(); + + { + m_Def.m_vsChoices.push_back( OFF ); + GameCommand mc; + mc.m_pCharacter = NULL; + m_aListEntries.push_back( mc ); + } + + vector vpCharacters; + CHARMAN->GetCharacters( vpCharacters ); + for( unsigned i=0; iGetDisplayName(); + s.MakeUpper(); + + m_Def.m_vsChoices.push_back( s ); + GameCommand mc; + mc.m_pCharacter = pCharacter; + m_aListEntries.push_back( mc ); + } + } +}; + +class OptionRowHandlerListStyles: public OptionRowHandlerList +{ + virtual void LoadInternal( const Commands & ) + { + m_Def.m_bOneChoiceForAllPlayers = true; + m_Def.m_sName = "Style"; + m_Def.m_bAllowThemeItems = false; // we theme the text ourself + + vector vStyles; + GAMEMAN->GetStylesForGame( GAMESTATE->m_pCurGame, vStyles ); + ASSERT( vStyles.size() != 0 ); + for (Style const *s : vStyles) + { + m_Def.m_vsChoices.push_back( GAMEMAN->StyleToLocalizedString(s) ); + GameCommand mc; + mc.m_pStyle = s; + m_aListEntries.push_back( mc ); + } + + m_Default.m_pStyle = vStyles[0]; + } +}; + +class OptionRowHandlerListGroups: public OptionRowHandlerList +{ + virtual void LoadInternal( const Commands & ) + { + m_Def.m_bOneChoiceForAllPlayers = true; + m_Def.m_bAllowThemeItems = false; // we theme the text ourself + m_Def.m_sName = "Group"; + m_Default.m_sSongGroup = GROUP_ALL; + + vector vSongGroups; + SONGMAN->GetSongGroupNames( vSongGroups ); + ASSERT( vSongGroups.size() != 0 ); + + { + m_Def.m_vsChoices.push_back( "AllGroups" ); + GameCommand mc; + mc.m_sSongGroup = GROUP_ALL; + m_aListEntries.push_back( mc ); + } + + for (RString const &g : vSongGroups) + { + m_Def.m_vsChoices.push_back( g ); + GameCommand mc; + mc.m_sSongGroup = g; + m_aListEntries.push_back( mc ); + } + } +}; + +class OptionRowHandlerListDifficulties: public OptionRowHandlerList +{ + virtual void LoadInternal( const Commands & ) + { + m_Def.m_bOneChoiceForAllPlayers = true; + m_Def.m_sName = "Difficulty"; + m_Default.m_dc = Difficulty_Invalid; + m_Def.m_bAllowThemeItems = false; // we theme the text ourself + + { + m_Def.m_vsChoices.push_back( "AllDifficulties" ); + GameCommand mc; + mc.m_dc = Difficulty_Invalid; + m_aListEntries.push_back( mc ); + } + + for (Difficulty const &d : CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue()) + { + // TODO: Is this the best thing we can do here? + StepsType st = GAMEMAN->GetHowToPlayStyleForGame( GAMESTATE->m_pCurGame )->m_StepsType; + RString s = CustomDifficultyToLocalizedString( GetCustomDifficulty(st, d, CourseType_Invalid) ); + + m_Def.m_vsChoices.push_back( s ); + GameCommand mc; + mc.m_dc = d; + m_aListEntries.push_back( mc ); + } + } +}; + +// XXX: very similar to OptionRowHandlerSongChoices +class OptionRowHandlerListSongsInCurrentSongGroup: public OptionRowHandlerList +{ + virtual void LoadInternal( const Commands & ) + { + const vector &vpSongs = SONGMAN->GetSongs( GAMESTATE->m_sPreferredSongGroup ); + + if( GAMESTATE->m_pCurSong == NULL ) + GAMESTATE->m_pCurSong.Set( vpSongs[0] ); + + m_Def.m_sName = "SongsInCurrentSongGroup"; + m_Def.m_bOneChoiceForAllPlayers = true; + m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; + m_Def.m_bExportOnChange = true; + + for (Song *p : vpSongs) + { + m_Def.m_vsChoices.push_back( p->GetTranslitFullTitle() ); + GameCommand mc; + mc.m_pSong = p; + m_aListEntries.push_back( mc ); + } + } +}; + +class OptionRowHandlerLua : public OptionRowHandler +{ +public: + LuaReference *m_pLuaTable; + LuaReference m_EnabledForPlayersFunc; + + OptionRowHandlerLua() { m_pLuaTable = new LuaReference; Init(); } + virtual ~OptionRowHandlerLua() { delete m_pLuaTable; } + void Init() + { + OptionRowHandler::Init(); + m_pLuaTable->Unset(); + } + + void SetEnabledForPlayers() + { + Lua *L = LUA->Get(); + + if( m_EnabledForPlayersFunc.IsNil() ) + { + LUA->Release(L); + return; + } + + m_EnabledForPlayersFunc.PushSelf( L ); + + // Argument 1 (self): + m_pLuaTable->PushSelf( L ); + + lua_call( L, 1, 1 ); // call function with 1 argument and 1 result + if( !lua_istable(L, -1) ) + RageException::Throw( "\"EnabledForPlayers\" did not return a table." ); + + m_Def.m_vEnabledForPlayers.clear(); // and fill in with supplied PlayerNumbers below + + lua_pushnil( L ); + while( lua_next(L, -2) != 0 ) + { + // `key' is at index -2 and `value' at index -1 + PlayerNumber pn = (PlayerNumber)luaL_checkint( L, -1 ); + + m_Def.m_vEnabledForPlayers.insert( pn ); + + lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration + } + lua_pop( L, 1 ); + + LUA->Release(L); + } + + virtual void LoadInternal( const Commands &cmds ) + { + ASSERT( cmds.v.size() == 1 ); + const Command &command = cmds.v[0]; + ASSERT( command.m_vsArgs.size() == 2 ); + RString sLuaFunction = command.m_vsArgs[1]; + ASSERT( sLuaFunction.size() != 0 ); + + m_Def.m_bAllowThemeItems = false; // Lua options are always dynamic and can theme themselves. + + Lua *L = LUA->Get(); + + // Run the Lua expression. It should return a table. + m_pLuaTable->SetFromExpression( sLuaFunction ); + + if( m_pLuaTable->GetLuaType() != LUA_TTABLE ) + RageException::Throw( "Result of \"%s\" is not a table.", sLuaFunction.c_str() ); + + m_pLuaTable->PushSelf( L ); + + lua_pushstring( L, "Name" ); + lua_gettable( L, -2 ); + const char *pStr = lua_tostring( L, -1 ); + if( pStr == NULL ) + RageException::Throw( "\"%s\" \"Name\" entry is not a string.", sLuaFunction.c_str() ); + m_Def.m_sName = pStr; + lua_pop( L, 1 ); + + lua_pushstring( L, "OneChoiceForAllPlayers" ); + lua_gettable( L, -2 ); + m_Def.m_bOneChoiceForAllPlayers = !!lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + + lua_pushstring( L, "ExportOnChange" ); + lua_gettable( L, -2 ); + m_Def.m_bExportOnChange = !!lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + + lua_pushstring( L, "LayoutType" ); + lua_gettable( L, -2 ); + pStr = lua_tostring( L, -1 ); + if( pStr == NULL ) + RageException::Throw( "\"%s\" \"LayoutType\" entry is not a string.", sLuaFunction.c_str() ); + m_Def.m_layoutType = StringToLayoutType( pStr ); + ASSERT( m_Def.m_layoutType != LayoutType_Invalid ); + lua_pop( L, 1 ); + + lua_pushstring( L, "SelectType" ); + lua_gettable( L, -2 ); + pStr = lua_tostring( L, -1 ); + if( pStr == NULL ) + RageException::Throw( "\"%s\" \"SelectType\" entry is not a string.", sLuaFunction.c_str() ); + m_Def.m_selectType = StringToSelectType( pStr ); + ASSERT( m_Def.m_selectType != SelectType_Invalid ); + lua_pop( L, 1 ); + + // Iterate over the "Choices" table. + lua_pushstring( L, "Choices" ); + lua_gettable( L, -2 ); + if( !lua_istable( L, -1 ) ) + RageException::Throw( "\"%s\" \"Choices\" is not a table.", sLuaFunction.c_str() ); + + lua_pushnil( L ); + while( lua_next(L, -2) != 0 ) + { + // `key' is at index -2 and `value' at index -1 + const char *pValue = lua_tostring( L, -1 ); + if( pValue == NULL ) + RageException::Throw( "\"%s\" Column entry is not a string.", sLuaFunction.c_str() ); +// LOG->Trace( "'%s'", pValue); + + m_Def.m_vsChoices.push_back( pValue ); + + lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration + } + + lua_pop( L, 1 ); // pop choices table + + // Set the EnabledForPlayers function. + lua_pushstring( L, "EnabledForPlayers" ); + lua_gettable( L, -2 ); + if( !lua_isfunction( L, -1 ) && !lua_isnil( L, -1 ) ) + RageException::Throw( "\"%s\" \"EnabledForPlayers\" is not a table.", sLuaFunction.c_str() ); + m_EnabledForPlayersFunc.SetFromStack( L ); + SetEnabledForPlayers(); + + // Iterate over the "ReloadRowMessages" table. + lua_pushstring( L, "ReloadRowMessages" ); + lua_gettable( L, -2 ); + if( !lua_isnil( L, -1 ) ) + { + if( !lua_istable( L, -1 ) ) + RageException::Throw( "\"%s\" \"ReloadRowMessages\" is not a table.", sLuaFunction.c_str() ); + + lua_pushnil( L ); + while( lua_next(L, -2) != 0 ) + { + // `key' is at index -2 and `value' at index -1 + const char *pValue = lua_tostring( L, -1 ); + if( pValue == NULL ) + RageException::Throw( "\"%s\" Column entry is not a string.", sLuaFunction.c_str() ); + LOG->Trace( "Found ReloadRowMessage '%s'", pValue); + + m_vsReloadRowMessages.push_back( pValue ); + + lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration + } + } + lua_pop( L, 1 ); // pop ReloadRowMessages table + + // Look for "ExportOnChange" value. + lua_pushstring( L, "ExportOnChange" ); + lua_gettable( L, -2 ); + if( !lua_isnil( L, -1 ) ) + { + m_Def.m_bExportOnChange = !!MyLua_checkboolean( L, -1 ); + } + lua_pop( L, 1 ); // pop ExportOnChange value + + lua_pop( L, 1 ); // pop main table + ASSERT( lua_gettop(L) == 0 ); + + LUA->Release(L); + } + + virtual ReloadChanged Reload() + { + SetEnabledForPlayers(); + return RELOAD_CHANGED_ENABLED; + } + + virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const + { + Lua *L = LUA->Get(); + + ASSERT( lua_gettop(L) == 0 ); + + for (PlayerNumber const &p : vpns) + { + vector &vbSelOut = vbSelectedOut[p]; + + /* Evaluate the LoadSelections(self,array,pn) function, where + * array is a table representing vbSelectedOut. */ + + // All selections default to false. + for( unsigned i = 0; i < vbSelOut.size(); ++i ) + vbSelOut[i] = false; + + // Create the vbSelectedOut table + LuaHelpers::CreateTableFromArrayB( L, vbSelOut ); + ASSERT( lua_gettop(L) == 1 ); // vbSelectedOut table + + // Get the function to call from m_LuaTable. + m_pLuaTable->PushSelf( L ); + ASSERT( lua_istable( L, -1 ) ); + + lua_pushstring( L, "LoadSelections" ); + lua_gettable( L, -2 ); + if( !lua_isfunction( L, -1 ) ) + RageException::Throw( "\"%s\" \"LoadSelections\" entry is not a function.", m_Def.m_sName.c_str() ); + + // Argument 1 (self): + m_pLuaTable->PushSelf( L ); + + // Argument 2 (vbSelectedOut): + lua_pushvalue( L, 1 ); + + // Argument 3 (pn): + LuaHelpers::Push( L, p ); + + ASSERT( lua_gettop(L) == 6 ); // vbSelectedOut, m_iLuaTable, function, self, arg, arg + + lua_call( L, 3, 0 ); // call function with 3 arguments and 0 results + ASSERT( lua_gettop(L) == 2 ); + + lua_pop( L, 1 ); // pop option table + + LuaHelpers::ReadArrayFromTableB( L, vbSelOut ); + + lua_pop( L, 1 ); // pop vbSelectedOut table + + ASSERT( lua_gettop(L) == 0 ); + } + + LUA->Release(L); + } + virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const + { + Lua *L = LUA->Get(); + + ASSERT( lua_gettop(L) == 0 ); + + for (PlayerNumber const &p : vpns) + { + const vector &vbSel = vbSelected[p]; + + /* Evaluate SaveSelections(self,array,pn) function, where array is + * a table representing vbSelectedOut. */ + + vector vbSelectedCopy = vbSel; + + // Create the vbSelectedOut table. + LuaHelpers::CreateTableFromArrayB( L, vbSelectedCopy ); + ASSERT( lua_gettop(L) == 1 ); // vbSelectedOut table + + // Get the function to call. + m_pLuaTable->PushSelf( L ); + ASSERT( lua_istable( L, -1 ) ); + + lua_pushstring( L, "SaveSelections" ); + lua_gettable( L, -2 ); + if( !lua_isfunction( L, -1 ) ) + RageException::Throw( "\"%s\" \"SaveSelections\" entry is not a function.", m_Def.m_sName.c_str() ); + + // Argument 1 (self): + m_pLuaTable->PushSelf( L ); + + // Argument 2 (vbSelectedOut): + lua_pushvalue( L, 1 ); + + // Argument 3 (pn): + LuaHelpers::Push( L, p ); + + ASSERT( lua_gettop(L) == 6 ); // vbSelectedOut, m_iLuaTable, function, self, arg, arg + + lua_call( L, 3, 0 ); // call function with 3 arguments and 0 results + ASSERT( lua_gettop(L) == 2 ); + + lua_pop( L, 1 ); // pop option table + lua_pop( L, 1 ); // pop vbSelected table + + ASSERT( lua_gettop(L) == 0 ); + } + + LUA->Release(L); + + // XXX: allow specifying the mask + return 0; + } +}; + +class OptionRowHandlerConfig : public OptionRowHandler +{ +public: + const ConfOption *m_pOpt; + + OptionRowHandlerConfig() { Init(); } + void Init() + { + OptionRowHandler::Init(); + m_pOpt = NULL; + } + virtual void LoadInternal( const Commands &cmds ) + { + ASSERT( cmds.v.size() == 1 ); + const Command &command = cmds.v[0]; + RString sParam = command.GetArg(1).s; + ASSERT( command.m_vsArgs.size() == 2 ); + ASSERT( sParam.size() != 0 ); + + Init(); + + // Configuration values are never per-player. + m_Def.m_bOneChoiceForAllPlayers = true; + + ConfOption *pConfOption = ConfOption::Find( sParam ); + if( pConfOption == NULL ) + { + LOG->Warn( "Invalid Conf type \"%s\"", sParam.c_str() ); + pConfOption = ConfOption::Find( "Invalid" ); + ASSERT_M( pConfOption != NULL, "ConfOption::Find(Invalid)" ); + } + + pConfOption->UpdateAvailableOptions(); + + m_pOpt = pConfOption; + m_pOpt->MakeOptionsList( m_Def.m_vsChoices ); + + m_Def.m_bAllowThemeItems = m_pOpt->m_bAllowThemeItems; + + m_Def.m_sName = m_pOpt->name; + } + virtual void ImportOption( OptionRow *, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const + { + for (PlayerNumber const &p : vpns) + { + vector &vbSelOut = vbSelectedOut[p]; + + int iSelection = m_pOpt->Get(); + OptionRowHandlerUtil::SelectExactlyOne( iSelection, vbSelOut ); + } + } + virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const + { + bool bChanged = false; + + for (PlayerNumber const &p : vpns) + { + const vector &vbSel = vbSelected[p]; + + int iSel = OptionRowHandlerUtil::GetOneSelection(vbSel); + + // Get the original choice. + int iOriginal = m_pOpt->Get(); + + // Apply. + m_pOpt->Put( iSel ); + + // Get the new choice. + int iNew = m_pOpt->Get(); + + // If it didn't change, don't return any side-effects. + if( iOriginal != iNew ) + bChanged = true; + } + + return bChanged ? m_pOpt->GetEffects() : 0; + } +}; + +class OptionRowHandlerStepsType : public OptionRowHandler +{ +public: + BroadcastOnChange *m_pstToFill; + vector m_vStepsTypesToShow; + + OptionRowHandlerStepsType() { Init(); } + void Init() + { + OptionRowHandler::Init(); + m_pstToFill = NULL; + m_vStepsTypesToShow.clear(); + } + + virtual void LoadInternal( const Commands &cmds ) + { + ASSERT( cmds.v.size() == 1 ); + const Command &command = cmds.v[0]; + RString sParam = command.GetArg(1).s; + ASSERT( command.m_vsArgs.size() == 2 ); + ASSERT( sParam.size() != 0 ); + + if( sParam == "EditStepsType" ) + { + m_pstToFill = &GAMESTATE->m_stEdit; + } + else if( sParam == "EditSourceStepsType" ) + { + m_pstToFill = &GAMESTATE->m_stEditSource; + m_vsReloadRowMessages.push_back( MessageIDToString(Message_CurrentStepsP1Changed) ); + m_vsReloadRowMessages.push_back( MessageIDToString(Message_EditStepsTypeChanged) ); + if( GAMESTATE->m_pCurSteps[0].Get() != NULL ) + m_Def.m_vEnabledForPlayers.clear(); // hide row + } + else + { + RageException::Throw( "Invalid StepsType param \"%s\".", sParam.c_str() ); + } + + m_Def.m_sName = sParam; + m_Def.m_bOneChoiceForAllPlayers = true; + m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; + m_Def.m_bExportOnChange = true; + m_Def.m_bAllowThemeItems = false; // we theme the text ourself + + // calculate which StepsTypes to show + m_vStepsTypesToShow = CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue(); + + m_Def.m_vsChoices.clear(); + for (StepsType const &st : m_vStepsTypesToShow) + { + RString s = GAMEMAN->GetStepsTypeInfo( st ).GetLocalizedString(); + m_Def.m_vsChoices.push_back( s ); + } + + if( *m_pstToFill == StepsType_Invalid ) + m_pstToFill->Set( m_vStepsTypesToShow[0] ); + } + + virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const + { + for (PlayerNumber const &p : vpns) + { + vector &vbSelOut = vbSelectedOut[p]; + + if( GAMESTATE->m_pCurSteps[0] ) + { + StepsType st = GAMESTATE->m_pCurSteps[0]->m_StepsType; + vector::const_iterator iter = find( m_vStepsTypesToShow.begin(), m_vStepsTypesToShow.end(), st ); + if( iter != m_vStepsTypesToShow.end() ) + { + unsigned i = iter - m_vStepsTypesToShow.begin(); + vbSelOut[i] = true; + continue; // done with this player + } + } + vbSelOut[0] = true; + } + } + virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const + { + for (PlayerNumber const &p : vpns) + { + const vector &vbSel = vbSelected[p]; + + int index = OptionRowHandlerUtil::GetOneSelection( vbSel ); + m_pstToFill->Set( m_vStepsTypesToShow[index] ); + } + + return 0; + } +}; + + +class OptionRowHandlerGameCommand : public OptionRowHandler +{ +public: + GameCommand m_gc; + + OptionRowHandlerGameCommand() { Init(); } + void Init() + { + OptionRowHandler::Init(); + m_gc.Init(); + m_gc.ApplyCommitsScreens( false ); + } + virtual void LoadInternal( const Commands &cmds ) + { + ASSERT( cmds.v.size() > 1 ); + + Commands temp = cmds; + temp.v.erase( temp.v.begin() ); + m_gc.Load( 0, temp ); + ASSERT( !m_gc.m_sName.empty() ); + m_Def.m_sName = m_gc.m_sName; + m_Def.m_bOneChoiceForAllPlayers = true; + m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; + m_Def.m_selectType = SELECT_NONE; + m_Def.m_vsChoices.push_back( "" ); + } + virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const + { + } + virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const + { + if( vbSelected[PLAYER_1][0] || vbSelected[PLAYER_2][0] ) + m_gc.ApplyToAllPlayers(); + return 0; + } + virtual void GetIconTextAndGameCommand( int iFirstSelection, RString &sIconTextOut, GameCommand &gcOut ) const + { + sIconTextOut = ""; + gcOut = m_gc; + } + virtual RString GetScreen( int iChoice ) const + { + return m_gc.m_sScreen; + } +}; + +class OptionRowHandlerNull: public OptionRowHandler +{ +public: + OptionRowHandlerNull() { Init(); } +}; + +/////////////////////////////////////////////////////////////////////////////////// + +OptionRowHandler* OptionRowHandlerUtil::Make( const Commands &cmds ) +{ + OptionRowHandler* pHand = NULL; + + if( cmds.v.size() == 0 ) + return NULL; + + const RString &name = cmds.v[0].GetName(); + +#define MAKE( type ) { type *p = new type; p->Load( cmds ); pHand = p; } + + // XXX: merge these, and merge "Steps" and "list,Steps" + if( name == "list" ) + { + const Command &command = cmds.v[0]; + RString sParam = command.GetArg(1).s; + if( command.m_vsArgs.size() != 2 || !sParam.size() ) + return NULL; + + if( sParam.CompareNoCase("NoteSkins")==0 ) MAKE( OptionRowHandlerListNoteSkins ) + else if( sParam.CompareNoCase("Steps")==0 ) MAKE( OptionRowHandlerListSteps ) + else if( sParam.CompareNoCase("StepsLocked")==0 ) + { + MAKE( OptionRowHandlerListSteps ); + pHand->m_Def.m_bOneChoiceForAllPlayers = true; + } + else if( sParam.CompareNoCase("Characters")==0 ) MAKE( OptionRowHandlerListCharacters ) + else if( sParam.CompareNoCase("Styles")==0 ) MAKE( OptionRowHandlerListStyles ) + else if( sParam.CompareNoCase("Groups")==0 ) MAKE( OptionRowHandlerListGroups ) + else if( sParam.CompareNoCase("Difficulties")==0 ) MAKE( OptionRowHandlerListDifficulties ) + else if( sParam.CompareNoCase("SongsInCurrentSongGroup")==0 ) MAKE( OptionRowHandlerListSongsInCurrentSongGroup ) + else MAKE( OptionRowHandlerList ) + } + else if( name == "lua" ) MAKE( OptionRowHandlerLua ) + else if( name == "conf" ) MAKE( OptionRowHandlerConfig ) + else if( name == "stepstype" ) MAKE( OptionRowHandlerStepsType ) + else if( name == "steps" ) MAKE( OptionRowHandlerSteps ) + else if( name == "gamecommand" ) MAKE( OptionRowHandlerGameCommand ) + + return pHand; +} + +OptionRowHandler* OptionRowHandlerUtil::MakeNull() +{ + OptionRowHandler* pHand = NULL; + Commands cmds; + MAKE( OptionRowHandlerNull ) + return pHand; +} + +OptionRowHandler* OptionRowHandlerUtil::MakeSimple( const MenuRowDef &mr ) +{ + OptionRowHandler *pHand = OptionRowHandlerUtil::MakeNull(); + + pHand->m_Def.m_sName = mr.sName; + FontCharAliases::ReplaceMarkers( pHand->m_Def.m_sName ); // Allow special characters + + pHand->m_Def.m_vEnabledForPlayers.clear(); + if( mr.pfnEnabled? mr.pfnEnabled():mr.bEnabled ) + { + FOREACH_EnabledPlayer( pn ) + pHand->m_Def.m_vEnabledForPlayers.insert( pn ); + } + + pHand->m_Def.m_bOneChoiceForAllPlayers = true; + pHand->m_Def.m_selectType = SELECT_ONE; + pHand->m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; + pHand->m_Def.m_bExportOnChange = false;//true; + + // MISTER CHOICES! + pHand->m_Def.m_vsChoices = mr.choices; + + // Each row must have at least one choice. + if( pHand->m_Def.m_vsChoices.empty() ) + pHand->m_Def.m_vsChoices.push_back( "" ); + + pHand->m_Def.m_bAllowThemeTitle = mr.bThemeTitle; + pHand->m_Def.m_bAllowThemeItems = mr.bThemeItems; + + FOREACH( RString, pHand->m_Def.m_vsChoices, c ) + FontCharAliases::ReplaceMarkers( *c ); // Allow special characters + + return pHand; +} + +/* + * (c) 2002-2004 Chris Danford + * All rights reserved. + * + * 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. + * + * 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. + */