From 045e9edcdddfde82089c6bd5079d9bf3c6729aec Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Thu, 19 Jun 2014 06:41:18 -0600 Subject: [PATCH] Added ArbSpeedMods function for a better way of setting speed modifiers. Added NotifyHandlerOfSelection to OptionRowHandler to support this. Changed exceptions thrown by OptionRowHandlerLua into log warnings because crashing out is not helpful to themers. Fixed SetEnabledForPlayers in OptionRowHandlerLua to correctly read the PlayerNumber enum. Removed silly git add Docs/Themerdocs/Examples/OptionRowHandlerLua.lua and second check for ExportOnChange. --- .../Examples/OptionRowHandlerLua.lua | 110 ++++++++++++++++ .../_fallback/Scripts/03 CustomSpeedMods.lua | 122 +++++++++++++++++- Themes/_fallback/metrics.ini | 2 +- Themes/default/metrics.ini | 2 +- src/OptionRow.cpp | 35 ++++- src/OptionRow.h | 7 +- src/OptionRowHandler.cpp | 111 ++++++++++++---- src/OptionRowHandler.h | 2 + src/ScreenOptions.cpp | 6 +- 9 files changed, 361 insertions(+), 36 deletions(-) create mode 100644 Docs/Themerdocs/Examples/OptionRowHandlerLua.lua diff --git a/Docs/Themerdocs/Examples/OptionRowHandlerLua.lua b/Docs/Themerdocs/Examples/OptionRowHandlerLua.lua new file mode 100644 index 0000000000..c466da3159 --- /dev/null +++ b/Docs/Themerdocs/Examples/OptionRowHandlerLua.lua @@ -0,0 +1,110 @@ +-- This will discuss how to create an option row in lua for use on a normal options screen. + +-- To try out this example, copy this file to Scripts/ and add the following +-- line to metrics.ini, on one of the options screens. Change the "1" to an +-- appropriate line name and make sure it's in the LineNames list for that +-- screen. +-- Line1="lua,FooMods()" + +-- When on the screen testing the example, flush the log and read it when +-- interacting with the option row. This example doesn't actually apply any +-- modifiers to the player, it just prints messages to the log file so you can +-- see what functions are being called. +-- Make sure you test this with Player 2, Player 1 can't interact with this +-- option row as part of the example. + +-- Comments explaining an element come before the element they explain. + +-- The function "FooMods" returns a table containing all the information the +-- option row handler needs to build the option row. +function FooMods() + return { + -- A string with the name of the row. This name will be localized using + -- the entry in the "OptionTitles" section of the language file. + Name= "Foo", + -- A boolean controlling whether the choice affects all players. + OneChoiceForAllPlayers= false, + -- A LayoutType enum value. "ShowAllInRow" shows all items in the row, + -- "ShowOneInRow" shows only the choice with focus is shown. + -- "ShowOneInRow" is forced if there are enough choices that they would go + -- off screen. + LayoutType= "ShowAllInRow", + -- A SelectType enum value. "SelectOne" allows only one choice to be + -- selected. "SelectMultiple" allows multiple to be selected. + -- "SelectNone" allows none to be selected. + SelectType= "SelectMultiple", + -- Optional function. If non-nil, this function must return a table of + -- PlayerNumbers that are allowed to use the row. + EnabledForPlayers= function(self) + Trace("FooMods:EnabledForPlayers() called.") + -- Leave out PLAYER_1 just for example. + return {PLAYER_2} + end, + -- A table of strings that are the names of choices. Choice names are not + -- localized. + Choices= {"a", "b", "c", "d"}, + -- Optional table. If non-nil, this table must contain a list of messages + -- this row should listen for. If one of the messages is recieved, the + -- row is reloaded and the EnabledForPlayers function is called if it is + -- non-nil. + ReloadRowMessages= {"ReloadFooMods"}, + -- LoadSelections should examine the player and figure out which options + -- on the row the player has selected. + -- self is the table returned by the original function used to create the + -- option row. (the table being created right now). + -- list is a table of bools, all initially false. Set them to true to + -- indicate which options are on. + -- pn is the PlayerNumber of the player the selections are for. + LoadSelections= function(self, list, pn) + Trace("FooMods:LoadSelections(" .. pn .. ")") + for i, choice in ipairs(self.Choices) do + -- Randomly set some to true just for an example. + if math.random(0, 1) == 1 then + Trace(choice .. " (" .. i .. ")" .. " set to true.") + list[i]= true + end + end + end, + -- SaveSelections should examin the list of what the player has selected + -- and apply the appropriate modifiers to the player. + -- Same args as LoadSelections + SaveSelections= function(self, list, pn) + Trace("FooMods:SaveSelections(" .. pn .. ")") + for i, choice in ipairs(self.Choices) do + if list[i] then + Trace(choice .. " (" .. i .. ")" .. " set to true.") + end + end + end, + -- Optional function. If non-nil, this function must take 3 parameters + -- (self, pn, choice), and return a bool. It is called when a player + -- selects an item in the row by pressing start. + -- self is the same as for LoadSelections. + -- pn is the PlayerNumber of the player that made the selection. + -- choice is the choice the player's cursor is on. + -- The return value should be true if the Choices table is changed. If it + -- is true, then LoadSelections will be called to update which choices are + -- underlined for each player. + -- This function is meant to provide a way for a menu to change the text of + -- its choices. If it returns true, LoadSelections will be called for each + -- player. If OneChoiceForAllPlayers is true, this function will be called + -- for each player, which means LoadSelections will be called twice for + -- each player. Well written code shouldn't have a problem with this. + NotifyOfSelection= function(self, pn, choice) + Trace("FooMods:NotifyOfSelection(" .. pn .. ", " .. choice .. ")") + -- Randomly decide whether to change, as an example. + local change= math.random(0, 3) + -- No change half the time, lengthen or clip strings the other half. + if change == 2 then + for i, choice in ipairs(self.Choices) do + self.Choices[i]= choice .. choice + end + elseif change == 3 then + for i, choice in ipairs(self.Choices) do + self.Choices[i]= choice:sub(1, 1) + end + end + return change >= 2 + end + } +end diff --git a/Themes/_fallback/Scripts/03 CustomSpeedMods.lua b/Themes/_fallback/Scripts/03 CustomSpeedMods.lua index 9ce066d813..7539c83165 100644 --- a/Themes/_fallback/Scripts/03 CustomSpeedMods.lua +++ b/Themes/_fallback/Scripts/03 CustomSpeedMods.lua @@ -243,10 +243,130 @@ function SpeedMods() state:SetPlayerOptions("ModsLevel_Preferred", self.Choices[1]) end } - setmetatable( t, t ) return t end +function ArbSpeedMods() + -- If players are allowed to join while this option row is active, problems will probably occur. + local ret= { + Name= "Speed", + LayoutType= "ShowAllInRow", + SelectType= "SelectMultiple", + OneChoiceForAllPlayers= false, + LoadSelections= function(self, list, pn) + -- The first values display the current status of the speed mod. + if pn == PLAYER_1 or self.NumPlayers == 1 then + list[1]= true + else + list[2]= true + end + end, + SaveSelections= function(self, list, pn) + local val= self.CurValues[pn] + local poptions= GAMESTATE:GetPlayerState(pn):GetPlayerOptions("ModsLevel_Preferred") + -- modify stage, song and current too so this will work in edit mode. + local stoptions= GAMESTATE:GetPlayerState(pn):GetPlayerOptions("ModsLevel_Stage") + local soptions= GAMESTATE:GetPlayerState(pn):GetPlayerOptions("ModsLevel_Song") + local coptions= GAMESTATE:GetPlayerState(pn):GetPlayerOptions("ModsLevel_Current") + if val.mode == "x" then + poptions:XMod(val.speed) + stoptions:XMod(val.speed) + soptions:XMod(val.speed) + coptions:XMod(val.speed) + elseif val.mode == "C" then + poptions:CMod(val.speed) + stoptions:CMod(val.speed) + soptions:CMod(val.speed) + coptions:CMod(val.speed) + else + poptions:MMod(val.speed) + stoptions:MMod(val.speed) + soptions:MMod(val.speed) + coptions:MMod(val.speed) + end + end, + NotifyOfSelection= function(self, pn, choice) + -- Adjust for the status elements + local real_choice= choice - self.NumPlayers + -- return true even though we didn't actually change anything so that the underlines will stay correct. + if real_choice < 1 then return true end + local val= self.CurValues[pn] + if real_choice < 5 then + local incs= {100, 25, -25, -100} + if val.mode == "x" then + val.speed= val.speed + (incs[real_choice] / 100) + else + val.speed= val.speed + incs[real_choice] + end + elseif real_choice == 5 then + if val.mode ~= "x" then + val.speed= val.speed / 100 + val.mode= "x" + end + elseif real_choice == 6 then + if val.mode == "x" then + val.speed= math.floor(val.speed * 100) + end + val.mode= "C" + elseif real_choice == 7 then + if val.mode == "x" then + val.speed= math.floor(val.speed * 100) + end + val.mode= "m" + end + self:GenChoices() + return true + end, + GenChoices= function(self) + -- We can't show different options to each player, so compromise by + -- only showing the xmod increments if one player is in that mode. + local show_x_incs= false + for pn, val in pairs(self.CurValues) do + if val.mode == "x" then + show_x_incs= true + end + end + if show_x_incs then + self.Choices= {"+1", "+.25", "-.25", "-1", "Xmod", "Cmod", "Mmod"} + else + self.Choices= {"+100", "+25", "-25", "-100", "Xmod", "Cmod", "Mmod"} + end + -- Insert the status element for P2 first so it will be second + for i, pn in ipairs({PLAYER_2, PLAYER_1}) do + local val= self.CurValues[pn] + if val then + if val.mode == "x" then + table.insert(self.Choices, 1, val.speed .. "x") + else + table.insert(self.Choices, 1, val.mode .. val.speed) + end + end + end + end, + CurValues= {}, -- for easy tracking of what speed the player wants + NumPlayers= 0 -- for ease when adjusting for the status elements. + } + for i, pn in ipairs(GAMESTATE:GetEnabledPlayers()) do + local poptions= GAMESTATE:GetPlayerState(pn):GetPlayerOptions("ModsLevel_Preferred") + local speed= nil + local mode= nil + if poptions:MaxScrollBPM() > 0 then + mode= "m" + speed= poptions:MaxScrollBPM() + elseif poptions:TimeSpacing() > 0 then + mode= "C" + speed= poptions:ScrollBPM() + else + mode= "x" + speed= poptions:ScrollSpeed() + end + ret.CurValues[pn]= {mode= mode, speed= speed} + ret.NumPlayers= ret.NumPlayers + 1 + end + ret:GenChoices() + return ret +end + --[[ CustomSpeedMods (c) 2013 StepMania team. diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index dad21231b0..c5e226edb1 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -3076,7 +3076,7 @@ PlayMusic=false TimerSeconds=30 # LineNames="1,2,3A,3B,4,5,6,R1,R2,7,8,9,10,11,12,13,14,16,17" -Line1="lua,SpeedMods()" +Line1="lua,ArbSpeedMods()" # Line1="list,Speed" Line2="list,Accel" Line3A="list,EffectsReceptor" diff --git a/Themes/default/metrics.ini b/Themes/default/metrics.ini index 1435f2789c..660b1dd4e8 100644 --- a/Themes/default/metrics.ini +++ b/Themes/default/metrics.ini @@ -2011,7 +2011,7 @@ SmallBanner6OffCommand= [ScreenEditOptions] LineNames="1,2,3,4,5,6,R1,R2,7,8,9,10,Attacks,11,12,13,14,15,16,SF" -Line1="lua,SpeedMods()" +Line1="lua,ArbSpeedMods()" LineSF="lua,OptionRowScreenFilter()" [StepsDisplayEdit] diff --git a/src/OptionRow.cpp b/src/OptionRow.cpp index 030cdfcd94..60f7fa1e66 100644 --- a/src/OptionRow.cpp +++ b/src/OptionRow.cpp @@ -148,7 +148,7 @@ void OptionRow::LoadExit() ChoicesChanged( RowType_Exit ); } -void OptionRow::ChoicesChanged( RowType type ) +void OptionRow::ChoicesChanged( RowType type, bool reset_focus ) { ASSERT_M( !m_pHand->m_Def.m_vsChoices.empty(), m_pHand->m_Def.m_sName + " has no choices" ); @@ -181,9 +181,13 @@ void OptionRow::ChoicesChanged( RowType type ) InitText( type ); - // When choices change, the old focus position is meaningless; reset it. - FOREACH_PlayerNumber( p ) - SetChoiceInRowWithFocus( p, 0 ); + // Lua can change the choices now, and when it does, we don't want to change focus. + if(reset_focus) + { + // When choices change, the old focus position is meaningless; reset it. + FOREACH_PlayerNumber( p ) + SetChoiceInRowWithFocus( p, 0 ); + } m_textTitle->SetText( GetRowTitle() ); } @@ -705,6 +709,7 @@ void OptionRow::SetOneSelection( PlayerNumber pn, int iChoice ) FOREACH( bool, vb, b ) *b = false; vb[iChoice] = true; + NotifyHandlerOfSelection(pn, iChoice); } void OptionRow::SetOneSharedSelection( int iChoice ) @@ -788,11 +793,31 @@ OptionRowDefinition &OptionRow::GetRowDef() return m_pHand->m_Def; } -void OptionRow::SetSelected( PlayerNumber pn, int iChoice, bool b ) +bool OptionRow::SetSelected( PlayerNumber pn, int iChoice, bool b ) { if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) pn = PLAYER_1; m_vbSelected[pn][iChoice] = b; + return NotifyHandlerOfSelection(pn, iChoice); +} + +bool OptionRow::NotifyHandlerOfSelection(PlayerNumber pn, int choice) +{ + bool changed= m_pHand->NotifyOfSelection(pn, choice); + if(changed) + { + ChoicesChanged(m_RowType, false); + vector vpns; + FOREACH_HumanPlayer( p ) + vpns.push_back( p ); + ImportOptions(vpns); + FOREACH_PlayerNumber(p) + { + PositionUnderlines(p); + } + UpdateEnabledDisabled(); + } + return changed; } void OptionRow::SetExitText( RString sExitText ) diff --git a/src/OptionRow.h b/src/OptionRow.h index c7e2817c95..7a2c825bca 100644 --- a/src/OptionRow.h +++ b/src/OptionRow.h @@ -76,7 +76,7 @@ public: RString GetRowTitle() const; - void ChoicesChanged( RowType type ); + void ChoicesChanged( RowType type, bool reset_focus= true ); void PositionUnderlines( PlayerNumber pn ); void PositionIcons( PlayerNumber pn ); void UpdateText( PlayerNumber pn ); @@ -96,7 +96,10 @@ public: void ResetFocusFromSelection( PlayerNumber pn ); bool GetSelected( PlayerNumber pn, int iChoice ) const; - void SetSelected( PlayerNumber pn, int iChoice, bool b ); + // SetSelected returns true if the choices changed because of setting. + bool SetSelected( PlayerNumber pn, int iChoice, bool b ); + + bool NotifyHandlerOfSelection(PlayerNumber pn, int choice); const OptionRowDefinition &GetRowDef() const; OptionRowDefinition &GetRowDef(); diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index 01f0d32fc9..646927c212 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -834,7 +834,9 @@ public: 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." ); + { + LOG->Warn("LUA_ERROR: \"EnabledForPlayers\" did not return a table." ); + } m_Def.m_vEnabledForPlayers.clear(); // and fill in with supplied PlayerNumbers below @@ -842,7 +844,7 @@ public: while( lua_next(L, -2) != 0 ) { // `key' is at index -2 and `value' at index -1 - PlayerNumber pn = (PlayerNumber)luaL_checkint( L, -1 ); + PlayerNumber pn = Enum::Check(L, -1); m_Def.m_vEnabledForPlayers.insert( pn ); @@ -869,7 +871,9 @@ public: m_pLuaTable->SetFromExpression( sLuaFunction ); if( m_pLuaTable->GetLuaType() != LUA_TTABLE ) - RageException::Throw( "Result of \"%s\" is not a table.", sLuaFunction.c_str() ); + { + LOG->Warn("LUA_ERROR: Result of \"%s\" is not a table.", sLuaFunction.c_str()); + } m_pLuaTable->PushSelf( L ); @@ -877,25 +881,29 @@ public: 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() ); + { + LOG->Warn("LUA_ERROR: \"%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 ); + 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 ); + 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() ); + { + LOG->Warn("LUA_ERROR: \"%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 ); @@ -904,7 +912,9 @@ public: lua_gettable( L, -2 ); pStr = lua_tostring( L, -1 ); if( pStr == NULL ) - RageException::Throw( "\"%s\" \"SelectType\" entry is not a string.", sLuaFunction.c_str() ); + { + LOG->Warn("LUA_ERROR: \"%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 ); @@ -913,7 +923,9 @@ public: lua_pushstring( L, "Choices" ); lua_gettable( L, -2 ); if( !lua_istable( L, -1 ) ) - RageException::Throw( "\"%s\" \"Choices\" is not a table.", sLuaFunction.c_str() ); + { + LOG->Warn("LUA_ERROR: \"%s\" \"Choices\" is not a table.", sLuaFunction.c_str()); + } lua_pushnil( L ); while( lua_next(L, -2) != 0 ) @@ -921,8 +933,10 @@ public: // `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); + { + LOG->Warn("LUA_ERROR: \"%s\" Column entry is not a string.", sLuaFunction.c_str()); + } + //LOG->Trace( "choice: '%s'", pValue); m_Def.m_vsChoices.push_back( pValue ); @@ -935,7 +949,9 @@ public: 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() ); + { + LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" is not a function.", sLuaFunction.c_str()); + } m_EnabledForPlayersFunc.SetFromStack( L ); SetEnabledForPlayers(); @@ -953,8 +969,10 @@ public: // `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); + { + LOG->Warn("LUA_ERROR: \"%s\" Column entry is not a string.", sLuaFunction.c_str()); + } + //LOG->Trace( "Found ReloadRowMessage '%s'", pValue); m_vsReloadRowMessages.push_back( pValue ); @@ -963,15 +981,6 @@ public: } 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 ); @@ -1013,7 +1022,9 @@ public: 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() ); + { + LOG->Warn("LUA_ERROR: \"%s\" \"LoadSelections\" entry is not a function.", m_Def.m_sName.c_str()); + } // Argument 1 (self): m_pLuaTable->PushSelf( L ); @@ -1067,7 +1078,9 @@ public: 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() ); + { + LOG->Warn("LUA_ERROR: \"%s\" \"SaveSelections\" entry is not a function.", m_Def.m_sName.c_str()); + } // Argument 1 (self): m_pLuaTable->PushSelf( L ); @@ -1094,6 +1107,54 @@ public: // XXX: allow specifying the mask return 0; } + virtual bool NotifyOfSelection(PlayerNumber pn, int choice) + { + Lua *L= LUA->Get(); + m_pLuaTable->PushSelf(L); + + lua_pushstring(L, "NotifyOfSelection"); + lua_gettable(L, -2); + bool changed= false; + if(lua_isfunction(L, -1)) + { + m_pLuaTable->PushSelf(L); + LuaHelpers::Push(L, pn); + // Convert choice to a lua index so it matches up with the Choices table. + lua_pushinteger(L, choice+1); + lua_call(L, 3, 1); + if(lua_toboolean(L, -1)) + { + lua_pop(L, 1); + changed= true; + m_Def.m_vsChoices.clear(); + // Iterate over the "Choices" table. + lua_pushstring( L, "Choices" ); + lua_gettable( L, -2 ); + if(!lua_istable(L, -1)) + { + LOG->Warn("\"%s\" \"Choices\" is not a table.", m_Def.m_sName.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) + { + LOG->Warn("\"%s\" Column entry is not a string.", m_Def.m_sName.c_str()); + } + //LOG->Trace( "choice: '%s'", pValue); + + m_Def.m_vsChoices.push_back( pValue ); + + lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration + } + } + } + lua_settop(L, 0); // Release has an assert that forces a clear stack. + LUA->Release(L); + return changed; + } }; class OptionRowHandlerConfig : public OptionRowHandler diff --git a/src/OptionRowHandler.h b/src/OptionRowHandler.h index 232ccafee6..4f438fc699 100644 --- a/src/OptionRowHandler.h +++ b/src/OptionRowHandler.h @@ -178,6 +178,8 @@ public: virtual int ExportOption( const vector &, const vector vbSelected[NUM_PLAYERS] ) const { return 0; } virtual void GetIconTextAndGameCommand( int iFirstSelection, RString &sIconTextOut, GameCommand &gcOut ) const; virtual RString GetScreen( int /* iChoice */ ) const { return RString(); } + // Exists so that a lua function can act on the selection. Returns true if the choices should be reloaded. + virtual bool NotifyOfSelection(PlayerNumber pn, int choice) { return false; } }; /** @brief Utilities for the OptionRowHandlers. */ diff --git a/src/ScreenOptions.cpp b/src/ScreenOptions.cpp index e32b21b7dc..a368adb77e 100644 --- a/src/ScreenOptions.cpp +++ b/src/ScreenOptions.cpp @@ -895,7 +895,11 @@ void ScreenOptions::ProcessMenuStart( const InputEventPlus &input ) { int iChoiceInRow = row.GetChoiceInRowWithFocus(pn); bool bSelected = !row.GetSelected( pn, iChoiceInRow ); - row.SetSelected( pn, iChoiceInRow, bSelected ); + bool changed= row.SetSelected( pn, iChoiceInRow, bSelected ); + if(changed) + { + AfterChangeValueOrRow(pn); + } if( bSelected ) m_SoundToggleOn.Play();