Fixed fake conflict in ThemeAndGamePrefs.lua.

This commit is contained in:
Kyzentun
2014-06-26 23:57:40 -06:00
16 changed files with 679 additions and 92 deletions
@@ -0,0 +1,141 @@
-- 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()"
-- Make sure you test this with Player 2, Player 1 can't interact with this
-- option row as part of the example.
-- 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.
-- 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 boolean controlling whether SaveSelections is called after every
-- change. If this is true, SaveSelections will be called every time
-- the player moves the cursor on the row.
ExportOnChange= 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.
-- A row that can't be used by one player can be confusing for the players
-- so consider carefully before using this.
-- This function will be called an extra time during loading to ensure
-- it returns a table.
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 examine 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
Trace("Not changing choices.")
else
Trace("Changing choices.")
end
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
-- Don't actually broadcast a reload message from here, do it from some
-- other place that actually has a reason to trigger a reload. This is
-- just here so that nothing needs to be added to a screen's lua files
-- for this example.
if math.random(0, 5) == 0 then
Trace("Broadcasting reload message.")
MESSAGEMAN:Broadcast("ReloadFooMods")
end
return change >= 2
end
}
end
+6
View File
@@ -401,6 +401,7 @@ Hide=Hide
Holds=Holds
Insert=Insert
Insert Credit=Insert Credit
Invalid=Invalid row definition.
Key Joy Mappings=Map keys and joystick buttons to game functions.
Language=Choose your language.
LifeDifficulty=Increase this value to cause your life meter to drain faster and refill slower.
@@ -478,6 +479,8 @@ SoundResampleQuality=Advanced: Select the resampling quality to use.
SoundVolume=Adjust the volume level during gameplay.
SoundVolumeAttract=Adjust the volume level during attract mode.
Speed=Speed
Speed Increment=Size of the smaller increments used to adjust the speed mod.
Speed Multiple=Multiplied by Speed Increment to calculate the size of the large increments used to adjust the speed mod.
StepMania Credits=The StepMania Development Team.
Steps=Steps
StretchBackgrounds=Toggle the stretching of backgrounds.
@@ -973,6 +976,7 @@ Insert=Insert
Insert Credit=Insert Credit
Insert beat and shift down=Insert beat and shift down
Insert Entry=Insert Entry
Invalid=Invalid
Invert notes' player=Invert player notes (routine only)
Jumps=Jumps
Key Joy Mappings=Config Key/Joy Mappings
@@ -1099,6 +1103,8 @@ SoundResampleQuality=Resampling Quality
SoundVolume=Sound Volume
SoundVolumeAttract=Attract Volume
Speed=Speed
Speed Increment=Speed Increment
Speed Multiple=Speed Multiple
Step Author=Step Author
Steps=Steps
Stream=Stream
+9
View File
@@ -34,6 +34,15 @@ function string:find_last(text)
end
end
-- Round to nearest integer.
function math.round(n)
if n > 0 then
return math.floor(n+0.5)
else
return math.ceil(n-0.5)
end
end
-- (c) 2006 Glenn Maynard
-- All rights reserved.
--
@@ -91,7 +91,6 @@ function OptionsRandomJukebox()
GAMESTATE:SetJukeboxUsesModifiers(val)
end
}
setmetatable( t, t )
return t
end
@@ -134,7 +133,6 @@ function OptionsWeight()
end
end,
}
setmetatable(t, t)
return t
end
+214 -1
View File
@@ -243,10 +243,223 @@ function SpeedMods()
state:SetPlayerOptions("ModsLevel_Preferred", self.Choices[1])
end
}
setmetatable( t, t )
return t
end
local default_speed_increment= 25
local default_speed_inc_multiple= 4
local function get_speed_increment()
local increment= default_speed_increment
if ReadGamePrefFromFile("SpeedIncrement") then
increment= tonumber(GetGamePref("SpeedIncrement")) or default_speed_increment
else
WriteGamePrefToFile("SpeedIncrement", increment)
end
return increment
end
local function get_speed_multiple()
local multiple= default_speed_inc_multiple
if ReadGamePrefFromFile("SpeedMultiple") then
multiple= tonumber(GetGamePref("SpeedMultiple")) or default_speed_inc_multiple
else
WriteGamePrefToFile("SpeedMultiple", multiple)
end
return multiple
end
function SpeedModIncSize()
-- An option row for controlling the size of the increment used by
-- ArbitrarySpeedMods.
local increment= get_speed_increment()
local ret= {
Name= "Speed Increment",
LayoutType= "ShowAllInRow",
SelectType= "SelectMultiple",
OneChoiceForAllPlayers= true,
LoadSelections= function(self, list, pn)
-- The first value is the status element, only it should be true.
list[1]= true
end,
SaveSelections= function(self, list, pn)
WriteGamePrefToFile("SpeedIncrement", increment)
end,
NotifyOfSelection= function(self, pn, choice)
-- return true even though we didn't actually change anything so that
-- the underlines will stay correct.
if choice == 1 then return true end
local incs= {10, 1, -1, -10}
local new_val= increment + incs[choice-1]
if new_val > 0 then
increment= new_val
end
self:GenChoices()
return true
end,
GenChoices= function(self)
self.Choices= {tostring(increment), "+10", "+1", "-1", "-10"}
end
}
ret:GenChoices()
return ret
end
function SpeedModIncMultiple()
-- An option row for controlling the size of the increment used by
-- ArbitrarySpeedMods.
local multiple= get_speed_multiple()
local ret= {
Name= "Speed Multiple",
LayoutType= "ShowAllInRow",
SelectType= "SelectMultiple",
OneChoiceForAllPlayers= true,
LoadSelections= function(self, list, pn)
-- The first value is the status element, only it should be true.
list[1]= true
end,
SaveSelections= function(self, list, pn)
WriteGamePrefToFile("SpeedMultiple", multiple)
end,
NotifyOfSelection= function(self, pn, choice)
-- return true even though we didn't actually change anything so that
-- the underlines will stay correct.
if choice == 1 then return true end
local incs= {5, 1, -1, -5}
local new_val= multiple + incs[choice-1]
if new_val > 0 then
multiple= new_val
end
self:GenChoices()
return true
end,
GenChoices= function(self)
self.Choices= {tostring(multiple), "+5", "+1", "-1", "-5"}
end
}
ret:GenChoices()
return ret
end
function ArbitrarySpeedMods()
-- If players are allowed to join while this option row is active, problems will probably occur.
local increment= get_speed_increment()
local multiple= get_speed_multiple()
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
local speed= val.speed / 100
poptions:XMod(speed)
stoptions:XMod(speed)
soptions:XMod(speed)
coptions:XMod(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 big_inc= increment * multiple
local incs= {big_inc, increment, -increment, -big_inc}
local new_val= val.speed + incs[real_choice]
if new_val > 0 then
val.speed= math.round(new_val)
end
elseif real_choice >= 5 then
val.mode= ({"x", "C", "m"})[real_choice - 4]
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
local big_inc= increment * multiple
local small_inc= increment
if show_x_incs then
big_inc= tostring(big_inc / 100)
small_inc= tostring(small_inc / 100)
else
big_inc= tostring(big_inc)
small_inc= tostring(small_inc)
end
self.Choices= {
"+" .. big_inc, "+" .. small_inc, "-" .. small_inc, "-" .. big_inc,
"Xmod", "Cmod", "Mmod"}
-- 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/100) .. "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= math.round(poptions:MaxScrollBPM())
elseif poptions:TimeSpacing() > 0 then
mode= "C"
speed= math.round(poptions:ScrollBPM())
else
mode= "x"
speed= math.round(poptions:ScrollSpeed() * 100)
end
ret.CurValues[pn]= {mode= mode, speed= speed}
ret.NumPlayers= ret.NumPlayers + 1
end
ret:GenChoices()
return ret
end
--[[
CustomSpeedMods (c) 2013 StepMania team.
@@ -121,7 +121,6 @@ function OptionRowProTiming()
setenv("ProTiming"..pname, val); --]]
end;
};
setmetatable( t, t );
return t;
end;
+4 -2
View File
@@ -2942,11 +2942,13 @@ Line20="conf,VisualDelaySeconds"
Fallback="ScreenOptionsServiceChild"
NextScreen="ScreenOptionsService"
PrevScreen="ScreenOptionsService"
LineNames="3,4,8,11,13,14,15,16,28,29,30,31"
LineNames="3,4,8,SI,SM,11,13,14,15,16,28,29,30,31"
#LineScore="lua,UserPrefScoringMode()"
Line3="conf,TimingWindowScale"
Line4="conf,LifeDifficulty"
Line8="lua,GamePrefDefaultFail()"
LineSI="lua,SpeedModIncSize()"
LineSM="lua,SpeedModIncMultiple()"
Line11="conf,AllowW1"
Line13="conf,HiddenSongs"
Line14="conf,EasterEggs"
@@ -3077,7 +3079,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,ArbitrarySpeedMods()"
# Line1="list,Speed"
Line2="list,Accel"
Line3A="list,EffectsReceptor"
+2 -2
View File
@@ -1327,7 +1327,7 @@ LineFlashyCombo="lua,ThemePrefRow('FlashyCombo')"
[ScreenOptionsGraphicsSound]
[ScreenOptionsAdvanced]
LineNames="3,4,8,11,13,14,16,28,29,30,RollCombo"
LineNames="3,4,8,SI,SM,11,13,14,16,28,29,30,RollCombo"
LineRollCombo="lua,ThemePrefRow('ComboOnRolls')"
[ScreenAppearanceOptions]
@@ -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,ArbitrarySpeedMods()"
LineSF="lua,OptionRowScreenFilter()"
[StepsDisplayEdit]
+14 -1
View File
@@ -2,8 +2,9 @@
#include "EnumHelper.h"
#include "LuaManager.h"
#include "RageUtil.h"
#include "RageLog.h"
int CheckEnum( lua_State *L, LuaReference &table, int iPos, int iInvalid, const char *szType, bool bAllowInvalid )
int CheckEnum( lua_State *L, LuaReference &table, int iPos, int iInvalid, const char *szType, bool bAllowInvalid, bool bAllowAnything )
{
luaL_checkany( L, iPos );
@@ -61,6 +62,18 @@ int CheckEnum( lua_State *L, LuaReference &table, int iPos, int iInvalid, const
LuaHelpers::Pop( L, sGot );
}
LuaHelpers::Push( L, ssprintf("Expected %s; got %s", szType, sGot.c_str() ) );
// There are a couple places where CheckEnum is used outside of a
// function called from lua. If we use lua_error from one of them,
// StepMania crashes out completely. bAllowAnything allows those places
// to avoid crashing over theme mistakes.
if(bAllowAnything)
{
RString errmsg;
LuaHelpers::Pop(L, errmsg);
LOG->Warn(errmsg.c_str());
lua_pop(L, 2);
return iInvalid;
}
lua_error( L );
}
int iRet = lua_tointeger( L, -1 );
+5 -3
View File
@@ -20,7 +20,8 @@ int CheckEnum(lua_State *L,
int iPos,
int iInvalid,
const char *szType,
bool bAllowInvalid);
bool bAllowInvalid,
bool bAllowAnything= false);
template<typename T>
struct EnumTraits
@@ -36,14 +37,15 @@ template<typename T> LuaReference EnumTraits<T>::EnumToString;
namespace Enum
{
template<typename T>
static T Check( lua_State *L, int iPos, bool bAllowInvalid = false )
static T Check( lua_State *L, int iPos, bool bAllowInvalid = false, bool bAllowAnything= false )
{
return (T) CheckEnum(L,
EnumTraits<T>::StringToEnum,
iPos,
EnumTraits<T>::Invalid,
EnumTraits<T>::szName,
bAllowInvalid);
bAllowInvalid,
bAllowAnything);
}
template<typename T>
static void Push( lua_State *L, T iVal )
+20
View File
@@ -213,6 +213,26 @@ inline bool MyLua_checkintboolean( lua_State *L, int iArg )
return MyLua_checkboolean( L, iArg );
}
// Checks the table at index to verify that it contains strings.
inline bool TableContainsOnlyStrings(lua_State* L, int index)
{
bool passed= true;
lua_pushnil(L);
while(lua_next(L, index) != 0)
{
// `key' is at index -2 and `value' at index -1
const char *pValue = lua_tostring(L, -1);
if(pValue == NULL)
{
// Was going to print an error to the log with the key that failed,
// but didn't want to pull in RageLog. -Kyz
passed= false;
}
lua_pop(L, 1);
}
return passed;
}
#define SArg(n) (luaL_checkstring(L,(n)))
#define BIArg(n) (MyLua_checkintboolean(L,(n)))
#define IArg(n) (luaL_checkint(L,(n)))
+30 -5
View File
@@ -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<PlayerNumber> vpns;
FOREACH_HumanPlayer( p )
vpns.push_back( p );
ImportOptions(vpns);
FOREACH_PlayerNumber(p)
{
PositionUnderlines(p);
}
UpdateEnabledDisabled();
}
return changed;
}
void OptionRow::SetExitText( RString sExitText )
+5 -2
View File
@@ -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();
+222 -72
View File
@@ -809,7 +809,10 @@ public:
LuaReference *m_pLuaTable;
LuaReference m_EnabledForPlayersFunc;
OptionRowHandlerLua() { m_pLuaTable = new LuaReference; Init(); }
bool m_TableIsSane;
OptionRowHandlerLua(): m_TableIsSane(false)
{ m_pLuaTable = new LuaReference; Init(); }
virtual ~OptionRowHandlerLua() { delete m_pLuaTable; }
void Init()
{
@@ -817,8 +820,133 @@ public:
m_pLuaTable->Unset();
}
bool SanityCheckTable(lua_State* L, RString& RowName)
{
if(m_pLuaTable->GetLuaType() != LUA_TTABLE)
{
LOG->Warn("LUA_ERROR: Result of \"%s\" is not a table.", RowName.c_str());
return false;
}
m_pLuaTable->PushSelf(L);
lua_getfield(L, -1, "Name");
const char *pStr = lua_tostring(L, -1);
if( pStr == NULL )
{
LOG->Warn("LUA_ERROR: \"%s\" \"Name\" entry is not a string.", RowName.c_str());
return false;
}
lua_pop(L, 1);
lua_getfield(L, -1, "LayoutType");
pStr = lua_tostring(L, -1);
if(pStr == NULL || StringToLayoutType(pStr) == LayoutType_Invalid)
{
LOG->Warn("LUA_ERROR: \"%s\" \"LayoutType\" entry is not a string.", RowName.c_str());
return false;
}
lua_pop(L, 1);
lua_getfield(L, -1, "SelectType");
pStr = lua_tostring(L, -1);
if(pStr == NULL || StringToSelectType(pStr) == SelectType_Invalid)
{
LOG->Warn("LUA_ERROR: \"%s\" \"SelectType\" entry is not a string.", RowName.c_str());
return false;
}
lua_pop(L, 1);
lua_getfield(L, -1, "Choices");
if(!lua_istable(L, -1))
{
LOG->Warn("LUA_ERROR: \"%s\" \"Choices\" is not a table.", RowName.c_str());
return false;
}
if(!TableContainsOnlyStrings(L, lua_gettop(L)))
{
LOG->Warn("LUA_ERROR: \"%s\" \"Choices\" table contains a non-string.", RowName.c_str());
return false;
}
lua_pop(L, 1);
lua_getfield(L, -1, "EnabledForPlayers");
if(!lua_isnil(L, -1))
{
if(!lua_isfunction(L, -1))
{
LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" is not a function.", RowName.c_str());
return false;
}
m_pLuaTable->PushSelf( L );
lua_call( L, 1, 1 ); // call function with 1 argument and 1 result
if(!lua_istable(L, -1))
{
LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" did not return a table.", RowName.c_str());
return false;
}
lua_pushnil(L);
while(lua_next(L, -2) != 0)
{
PlayerNumber pn= Enum::Check<PlayerNumber>(L, -1, true, true);
if(pn == PlayerNumber_Invalid)
{
LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" contains a non-PlayerNumber.", RowName.c_str());
return false;
}
lua_pop(L, 1);
}
}
lua_pop(L, 1);
lua_getfield(L, -1, "ReloadRowMessages");
if(!lua_isnil(L, -1))
{
if(!lua_istable(L, -1))
{
LOG->Warn("LUA_ERROR: \"%s\" \"ReloadRowMessages\" is not a table.", RowName.c_str());
return false;
}
if(!TableContainsOnlyStrings(L, lua_gettop(L)))
{
LOG->Warn("LUA_ERROR: \"%s\" \"ReloadRowMessages\" table contains a non-string.", RowName.c_str());
return false;
}
}
lua_pop(L, 1);
lua_getfield(L, -1, "LoadSelections");
if(!lua_isfunction(L, -1))
{
LOG->Warn("LUA_ERROR: \"%s\" \"LoadSelections\" entry is not a function.", RowName.c_str());
return false;
}
lua_pop(L, 1);
lua_getfield(L, -1, "SaveSelections");
if(!lua_isfunction(L, -1))
{
LOG->Warn("LUA_ERROR: \"%s\" \"SaveSelections\" entry is not a function.", RowName.c_str());
return false;
}
lua_pop(L, 1);
lua_getfield(L, -1, "NotifyOfSelection");
if(!lua_isnil(L, -1) && !lua_isfunction(L, -1))
{
LOG->Warn("LUA_ERROR: \"%s\" \"NotifyOfSelection\" entry is not a function.", RowName.c_str());
return false;
}
lua_pop(L, 1);
lua_pop(L, 1);
return true;
}
void SetEnabledForPlayers()
{
if(!m_TableIsSane)
{
return;
}
Lua *L = LUA->Get();
if( m_EnabledForPlayersFunc.IsNil() )
@@ -833,16 +961,13 @@ public:
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 );
PlayerNumber pn = Enum::Check<PlayerNumber>(L, -1);
m_Def.m_vEnabledForPlayers.insert( pn );
@@ -867,111 +992,94 @@ public:
// Run the Lua expression. It should return a table.
m_pLuaTable->SetFromExpression( sLuaFunction );
m_TableIsSane= SanityCheckTable(L, sLuaFunction);
if(!m_TableIsSane)
{
m_pLuaTable->PushSelf(L);
lua_getfield(L, -1, "Name");
const char *pStr = lua_tostring( L, -1 );
if(pStr == NULL)
{
m_Def.m_sName = "Invalid";
}
else
{
m_Def.m_sName = pStr;
}
lua_pop( L, 1 );
// Add a fake choice so that there won't be a crash.
// This is so that a themer that makes a mistake doesn't have to
// completely restart and can just reload scripts.
m_Def.m_vsChoices.push_back("Error in row.");
// Set m_selectType to SELECT_MULTIPLE so we won't hit the assert in
// VerifySelected.
m_Def.m_selectType= SELECT_MULTIPLE;
lua_settop(L, 0); // Release has an assert that forces a clear stack.
LUA->Release(L);
return;
}
m_pLuaTable->PushSelf(L);
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 );
lua_getfield(L, -1, "Name");
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_getfield(L, -1, "OneChoiceForAllPlayers");
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_getfield(L, -1, "ExportOnChange");
m_Def.m_bExportOnChange = lua_toboolean( L, -1 );
lua_pop( L, 1 );
lua_pushstring( L, "LayoutType" );
lua_gettable( L, -2 );
// TODO: Change these to use the proper enum strings like everything
// else. This will break theme compatibility, so it has to wait until
// after SM5. -Kyz
lua_getfield(L, -1, "LayoutType");
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 );
lua_getfield(L, -1, "SelectType");
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_getfield(L, -1, "Choices");
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);
//LOG->Trace( "choice: '%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() );
lua_getfield(L, -1, "EnabledForPlayers");
m_EnabledForPlayersFunc.SetFromStack( L );
SetEnabledForPlayers();
// Iterate over the "ReloadRowMessages" table.
lua_pushstring( L, "ReloadRowMessages" );
lua_gettable( L, -2 );
lua_getfield(L, -1, "ReloadRowMessages");
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);
//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 );
@@ -986,6 +1094,10 @@ public:
virtual void ImportOption( OptionRow *pRow, const vector<PlayerNumber> &vpns, vector<bool> vbSelectedOut[NUM_PLAYERS] ) const
{
if(!m_TableIsSane)
{
return;
}
Lua *L = LUA->Get();
ASSERT( lua_gettop(L) == 0 );
@@ -1010,10 +1122,7 @@ public:
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() );
lua_getfield(L, -1, "LoadSelections");
// Argument 1 (self):
m_pLuaTable->PushSelf( L );
@@ -1042,6 +1151,10 @@ public:
}
virtual int ExportOption( const vector<PlayerNumber> &vpns, const vector<bool> vbSelected[NUM_PLAYERS] ) const
{
if(!m_TableIsSane)
{
return 0;
}
Lua *L = LUA->Get();
ASSERT( lua_gettop(L) == 0 );
@@ -1064,10 +1177,7 @@ public:
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() );
lua_getfield(L, -1, "SaveSelections");
// Argument 1 (self):
m_pLuaTable->PushSelf( L );
@@ -1094,6 +1204,46 @@ public:
// XXX: allow specifying the mask
return 0;
}
virtual bool NotifyOfSelection(PlayerNumber pn, int choice)
{
if(!m_TableIsSane)
{
return false;
}
Lua *L= LUA->Get();
m_pLuaTable->PushSelf(L);
lua_getfield(L, -1, "NotifyOfSelection");
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_getfield(L, -1, "Choices");
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 );
//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
+2
View File
@@ -178,6 +178,8 @@ public:
virtual int ExportOption( const vector<PlayerNumber> &, const vector<bool> 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. */
+5 -1
View File
@@ -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();