diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 1887d83591..6392011c2a 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -4,6 +4,32 @@ The StepMania 5 Changelog covers all post-sm-ssc changes. For a list of changes from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt. ________________________________________________________________________________ +2015/01/20 +---------- +* [ScreenSelect] ChoiceNames can be a lua command that is evaluated to get a + list of the gamecommands. The main use of this is to get rid of the huge + list of metrics that ScreenSelectStyle requires. A single lua function + instead of a metric for every style for every game type. [kyzentun] +* [ScreenSelectMaster] IconChoicePosFunction metric added. This metric is + optional, if it's set, it must be set to a function that returns a table + of positions. Each position is a table of the form "{x, y, z}", where x, + y, and z are numbers and any that is not a number defaults to 0. + The function is passed the number of choices for the screen. + IconChoiceOnCommand and IconChoiceOffCommand metrics added so each choice + doesn't require its own On/OffCommand pair. These are also optional, but + if they are set, they will override the individual commands if they exist. + (If "IconChoiceCactusOnCommand" and "IconChoiceOnCommand" both exist, + "IconChoiceOnCommand" will be used.) [kyzentun] + Example: + # in metrics.ini + IconChoicePosFunction=choice_positions + -- In a file in Scripts/ + function choice_positions(count) + local ret= {} + for i= 1, ret do ret[i]= {i*24, i*48} end + return ret + end + 2014/12/10 ---------- * [EditMode] Play whole song and play from current beat will now play until @@ -22,20 +48,20 @@ ________________________________________________________________________________ * [GameManager] Lua Scripts/ folders are now reloaded when the Game mode is changed. [kyzentun] * [GameState] CanSafelyEnterGameplay function added. [kyzentun] - In kickbox game mode, players can have different styles set, pass a - PlayerNumber when using SetCurrentStyle or GetCurrentStyle. + In kickbox game mode, players can have different styles set, pass a + PlayerNumber when using SetCurrentStyle or GetCurrentStyle. * [kickbox] New game mode with 4 styles. [kyzentun] * [ScreenGameplay] Notefield positioning is handled a bit differently now. See comments in _fallback/metrics.ini above the [ScreenGameplay] MarginFunction metric. Basically, you can define a lua function that returns the space you want at the edges and in the center instead of setting position metrics. Do NOT use Style:GetWidth to calculate the - margins your function returns. - ScreenGameplay now handles zooming the notefield to fit in the space - instead of using the NeedsZoomOutWith2Players flag in the style. - If the notefield doesn't fit in the space between the margins, and there is - only one player, the player will be centered even if the Center1Player pref - is false. [kyzentun] + margins your function returns. + ScreenGameplay now handles zooming the notefield to fit in the space + instead of using the NeedsZoomOutWith2Players flag in the style. + If the notefield doesn't fit in the space between the margins, and there is + only one player, the player will be centered even if the Center1Player pref + is false. [kyzentun] * [Style] NeedsZoomOutWith2Players decapitated. Always returns false now. GetWidth added. [kyzentun] * [techno] Techno game mode no longer has special column width for versus. @@ -74,7 +100,7 @@ ________________________________________________________________________________ a new profile was created fixed. [kyzentun] * [Everything] Actor and singleton functions that formerly returned nothing now return the actor or singleton. Example (set zoom, x, y, and diffuse in one): - self:zoom(.5):xy(_screen.cx, _screen.h-60):diffuse(color("#dc322f")) + self:zoom(.5):xy(_screen.cx, _screen.h-60):diffuse(color("#dc322f")) This also works for PlayerOptions and SongOptions API functions. Pass true as an extra arg, and they can be chained. diff --git a/Themes/_fallback/Scripts/03 Gameplay.lua b/Themes/_fallback/Scripts/03 Gameplay.lua index 339a3c0bd8..567fa354b4 100644 --- a/Themes/_fallback/Scripts/03 Gameplay.lua +++ b/Themes/_fallback/Scripts/03 Gameplay.lua @@ -107,6 +107,46 @@ function GameCompatibleModes() return Modes[CurGameName()] end +local function upper_first_letter(s) + local first_letter= s:match("([a-zA-Z])") + return s:gsub(first_letter, first_letter:upper(), 1) +end + +-- No more having a metric for every style for every game mode. -Kyz +function ScreenSelectStyleChoices() + local styles= GAMEMAN:GetStylesForGame(GAMESTATE:GetCurrentGame():GetName()) + local choices= {} + for i, style in ipairs(styles) do + local name= style:GetName() + local cap_name= upper_first_letter(name) + choices[#choices+1]= "name," .. cap_name .. ";style," .. name .. ";text," + .. cap_name .. ";screen," .. Branch.AfterSelectStyle() + end + return choices +end + +-- No more having an xy for every style for every game mode. -Kyz +function ScreenSelectStylePositions(count) + local poses= {} + local columns= 1 + if count > 4 then columns= 2 end + local start_y= _screen.cy - (_screen.h / (math.ceil(count/columns) / 2)) + for i= 1, count do + poses[i]= {} + if i <= count/columns then + poses[i][1]= _screen.cx - 160 + poses[i][2]= start_y + (96 * i) + else + poses[i][1]= _screen.cx + 160 + poses[i][2]= start_y + (96 * (i-(count/2))) + end + if columns == 1 then + poses[i][1]= _screen.cx + end + end + return poses +end + function SelectProfileKeys() local sGame = CurGameName() if sGame == "pump" then diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index 3b10eb0549..c955ba6a6e 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -1944,6 +1944,12 @@ PrevScreen=Branch.TitleMenu() TimerSeconds=30 # DefaultChoice="Single" +# Giant metric list left in for backwards compatibility. +# A much better way to handle this is: +# ChoiceNames="lua,ScreenSelectStyleChoices()" +# That will list all the styles for the current game without needing a huge +# list of metrics. _fallback still uses the old method to avoid the +# possibility of breaking themes. -Kyz ChoiceNames=GameCompatibleModes() # OptionOrderAuto="1:2,2:1" @@ -1975,11 +1981,6 @@ ChoiceVersus8="name,Versus8;style,versus8;screen,"..Branch.AfterSelectStyle() ChoiceDouble4="name,Double4;style,double4;screen,"..Branch.AfterSelectStyle() ChoiceDouble5="name,Double5;style,double5;screen,"..Branch.AfterSelectStyle() ChoiceDouble8="name,Double8;style,double8;screen,"..Branch.AfterSelectStyle() -# kickbox -ChoiceHuman="name,Human;style,human;screen,"..Branch.AfterSelectStyle() -ChoiceQuadarm="name,Quadarm;style,quadarm;screen,"..Branch.AfterSelectStyle() -ChoiceInsect="name,Insect;style,insect;screen,"..Branch.AfterSelectStyle() -ChoiceArachnid="name,Arachnid;style,arachnid;screen,"..Branch.AfterSelectStyle() # PerChoiceScrollElement=false PerChoiceIconElement=false diff --git a/Themes/default/metrics.ini b/Themes/default/metrics.ini index dc6b0a6018..c64c47b865 100644 --- a/Themes/default/metrics.ini +++ b/Themes/default/metrics.ini @@ -771,10 +771,8 @@ FOV=90 PerChoiceScrollElement=false PerChoiceIconElement=false # -Choice7Keys="name,7Keys;style,single7;screen,"..Branch.AfterSelectStyle() -Choice10Keys="name,10Keys;style,double5;screen,"..Branch.AfterSelectStyle() -Choice14Keys="name,14Keys;style,double7;screen,"..Branch.AfterSelectStyle() -ChoiceKB7="name,kb7;style,single;screen,"..Branch.AfterSelectStyle() +# Having one lua function is so much better than dozens of metrics. -Kyz +ChoiceNames="lua,ScreenSelectStyleChoices()" # ShowScroller=false ShowIcon=true @@ -784,65 +782,10 @@ UseIconMetrics=true IconGainFocusCommand=stoptweening;bounceend,0.05;zoom,1; IconLoseFocusCommand=stoptweening;decelerate,0.1;zoom,0.8; # -IconChoiceSingleX=SCREEN_CENTER_X-160 -IconChoiceSingleY=SCREEN_CENTER_Y-96 -IconChoiceSingleOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoiceSingleOffCommand=linear,0.05;zoomx,0 +IconChoicePosFunction=ScreenSelectStylePositions +IconChoiceOnCommand=zoom,0;bounceend,0.35;zoom,1 +IconChoiceOffCommand=linear,0.05;zoomx,0 # -IconChoiceDoubleX=SCREEN_CENTER_X-160 -IconChoiceDoubleY=SCREEN_CENTER_Y -IconChoiceDoubleOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoiceDoubleOffCommand=linear,0.05;zoomx,0 -#### -IconChoiceSoloX=SCREEN_CENTER_X-160 -IconChoiceSoloY=SCREEN_CENTER_Y+96 -IconChoiceSoloOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoiceSoloOffCommand=linear,0.05;zoomx,0 -# -IconChoiceHalfDoubleX=SCREEN_CENTER_X-160 -IconChoiceHalfDoubleY=SCREEN_CENTER_Y+96 -IconChoiceHalfDoubleOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoiceHalfDoubleOffCommand=linear,0.05;zoomx,0 -#### -IconChoiceVersusX=SCREEN_CENTER_X+160 -IconChoiceVersusY=string.find(THEME:GetMetric("ScreenSelectStyle","ChoiceNames"),"Routine") and SCREEN_CENTER_Y-96 or SCREEN_CENTER_Y-48 -IconChoiceVersusOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoiceVersusOffCommand=linear,0.05;zoomx,0 -# -IconChoiceCoupleX=SCREEN_CENTER_X+160 -IconChoiceCoupleY=string.find(THEME:GetMetric("ScreenSelectStyle","ChoiceNames"),"Routine") and SCREEN_CENTER_Y or SCREEN_CENTER_Y+48 -IconChoiceCoupleOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoiceCoupleOffCommand=linear,0.05;zoomx,0 -# -IconChoiceRoutineX=SCREEN_CENTER_X+160 -IconChoiceRoutineY=SCREEN_CENTER_Y+96 -IconChoiceRoutineOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoiceRoutineOffCommand=linear,0.05;zoomx,0 -# -IconChoicekb7X=SCREEN_CENTER_X -IconChoicekb7Y=SCREEN_CENTER_Y -IconChoicekb7OnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoicekb7OffCommand=linear,0.05;zoomx,0 -# -IconChoice5KeysX=SCREEN_CENTER_X-160 -IconChoice5KeysY=SCREEN_CENTER_Y-48 -IconChoice5KeysOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoice5KeysOffCommand=linear,0.05;zoomx,0 -# -IconChoice7KeysX=SCREEN_CENTER_X-160 -IconChoice7KeysY=SCREEN_CENTER_Y+48 -IconChoice7KeysOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoice7KeysOffCommand=linear,0.05;zoomx,0 -# -IconChoice10KeysX=SCREEN_CENTER_X+160 -IconChoice10KeysY=SCREEN_CENTER_Y-48 -IconChoice10KeysOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoice10KeysOffCommand=linear,0.05;zoomx,0 -# -IconChoice14KeysX=SCREEN_CENTER_X+160 -IconChoice14KeysY=SCREEN_CENTER_Y+48 -IconChoice14KeysOnCommand=zoom,0;bounceend,0.35;zoom,1 -IconChoice14KeysOffCommand=linear,0.05;zoomx,0 [ScreenSelectPlayMode] PersistScreens="ScreenSelectPlayMode,ScreenSelectMusic" diff --git a/src/Actor.cpp b/src/Actor.cpp index 402899c411..97260f2364 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -1300,9 +1300,9 @@ void Actor::QueueMessage( const RString& sMessageName ) TI.m_sCommandName = "!" + sMessageName; } -void Actor::AddCommand( const RString &sCmdName, apActorCommands apac ) +void Actor::AddCommand( const RString &sCmdName, apActorCommands apac, bool warn ) { - if( HasCommand(sCmdName) ) + if( HasCommand(sCmdName) && warn) { RString sWarning = GetLineage()+"'s command '"+sCmdName+"' defined twice"; LuaHelpers::ReportScriptError(sWarning, "COMMAND_DEFINED_TWICE"); diff --git a/src/Actor.h b/src/Actor.h index 2be5f711bd..beff135414 100644 --- a/src/Actor.h +++ b/src/Actor.h @@ -577,7 +577,7 @@ public: virtual void PushContext( lua_State *L ); // Named commands - void AddCommand( const RString &sCmdName, apActorCommands apac ); + void AddCommand( const RString &sCmdName, apActorCommands apac, bool warn= true ); bool HasCommand( const RString &sCmdName ) const; const apActorCommands *GetCommand( const RString &sCommandName ) const; void PlayCommand( const RString &sCommandName ) { HandleMessage( Message(sCommandName) ); } // convenience diff --git a/src/GameManager.cpp b/src/GameManager.cpp index 5ae0657765..95692c90e4 100644 --- a/src/GameManager.cpp +++ b/src/GameManager.cpp @@ -3163,12 +3163,12 @@ static const Style g_Style_Kickbox_Arachnid_Versus= static const Style* g_apGame_Kickbox_Styles[] = { &g_Style_Kickbox_Human, - &g_Style_Kickbox_Human_Versus, &g_Style_Kickbox_Quadarm, - &g_Style_Kickbox_Quadarm_Versus, &g_Style_Kickbox_Insect, - &g_Style_Kickbox_Insect_Versus, &g_Style_Kickbox_Arachnid, + &g_Style_Kickbox_Human_Versus, + &g_Style_Kickbox_Quadarm_Versus, + &g_Style_Kickbox_Insect_Versus, &g_Style_Kickbox_Arachnid_Versus, NULL }; diff --git a/src/GameState.cpp b/src/GameState.cpp index a6a0c6b530..c7de949139 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -998,7 +998,7 @@ void GameState::SetCompatibleStylesForPlayers() SetCurrentStyle(style, PLAYER_INVALID); } } - else + else if(GetCurrentStyle(PLAYER_INVALID) == NULL) { vector vst; GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst); @@ -1009,37 +1009,26 @@ void GameState::SetCompatibleStylesForPlayers() } if(!style_set) { - if(GetCurrentGame()->m_PlayersHaveSeparateStyles) + FOREACH_EnabledPlayer(pn) { - FOREACH_EnabledPlayer(pn) + StepsType st= StepsType_Invalid; + if(m_pCurSteps[pn] != NULL) { - StepsType st= StepsType_Invalid; - if(m_pCurSteps[pn] != NULL) - { - st= m_pCurSteps[pn]->m_StepsType; - } - else if(m_pCurTrail[pn] != NULL) - { - st= m_pCurTrail[pn]->m_StepsType; - } - else - { - vector vst; - GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst); - st= vst[0]; - } - const Style *style = GAMEMAN->GetFirstCompatibleStyle( - m_pCurGame, GetNumSidesJoined(), st); - SetCurrentStyle(style, pn); + st= m_pCurSteps[pn]->m_StepsType; + } + else if(m_pCurTrail[pn] != NULL) + { + st= m_pCurTrail[pn]->m_StepsType; + } + else + { + vector vst; + GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst); + st= vst[0]; } - } - else - { - vector vst; - GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst); const Style *style = GAMEMAN->GetFirstCompatibleStyle( - m_pCurGame, GetNumSidesJoined(), vst[0]); - SetCurrentStyle(style, PLAYER_INVALID); + m_pCurGame, GetNumSidesJoined(), st); + SetCurrentStyle(style, pn); } } } diff --git a/src/ScreenSelect.cpp b/src/ScreenSelect.cpp index 58c046bf27..45e84dcb35 100644 --- a/src/ScreenSelect.cpp +++ b/src/ScreenSelect.cpp @@ -31,6 +31,46 @@ void ScreenSelect::Init() this->SubscribeToMessage( Message_PlayerJoined ); // Load choices + // Allow lua as an alternative to metrics. + RString choice_names= CHOICE_NAMES; + if(choice_names.Left(4) == "lua,") + { + RString command= choice_names.Right(choice_names.size()-4); + Lua* L= LUA->Get(); + if(LuaHelpers::RunExpression(L, command, m_sName + "::ChoiceNames")) + { + if(!lua_istable(L, 1)) + { + LuaHelpers::ReportScriptError(m_sName + "::ChoiceNames expression did not return a table of gamecommands."); + } + else + { + size_t len= lua_objlen(L, 1); + for(size_t i= 1; i <= len; ++i) + { + lua_rawgeti(L, 1, i); + if(!lua_isstring(L, -1)) + { + LuaHelpers::ReportScriptErrorFmt(m_sName + "::ChoiceNames element %zu is not a string.", i); + } + else + { + RString com= SArg(-1); + GameCommand mc; + mc.ApplyCommitsScreens(false); + mc.m_sName = ssprintf("%zu", i); + Commands cmd= ParseCommands(com); + mc.Load(i, cmd); + m_aGameCommands.push_back(mc); + } + lua_pop(L, 1); + } + } + } + lua_settop(L, 0); + LUA->Release(L); + } + else { // Instead of using NUM_CHOICES, use a comma-separated list of choices. // Each element in the list is a choice name. This level of indirection diff --git a/src/ScreenSelectMaster.cpp b/src/ScreenSelectMaster.cpp index 5aa4c15423..b8056b2bb1 100644 --- a/src/ScreenSelectMaster.cpp +++ b/src/ScreenSelectMaster.cpp @@ -104,6 +104,68 @@ void ScreenSelectMaster::Init() FOREACH( PlayerNumber, vpns, p ) m_vsprScroll[*p].resize( m_aGameCommands.size() ); + vector positions; + bool positions_set_by_lua= false; + if(THEME->HasMetric(m_sName, "IconChoicePosFunction")) + { + positions_set_by_lua= true; + LuaReference command= THEME->GetMetricR(m_sName, "IconChoicePosFunction"); + if(command.GetLuaType() != LUA_TFUNCTION) + { + LuaHelpers::ReportScriptError(m_sName+"::IconChoicePosFunction must be a function."); + positions_set_by_lua= false; + } + else + { + Lua* L= LUA->Get(); + command.PushSelf(L); + lua_pushnumber(L, m_aGameCommands.size()); + RString err= m_sName + "::IconChoicePosFunction: "; + if(!LuaHelpers::RunScriptOnStack(L, err, 1, 1, true)) + { + positions_set_by_lua= false; + } + else + { + if(!lua_istable(L, -1)) + { + LuaHelpers::ReportScriptError(m_sName+"::IconChoicePosFunction did not return a table of positions."); + positions_set_by_lua= false; + } + else + { + size_t poses= lua_objlen(L, -1); + for(size_t p= 1; p <= poses; ++p) + { + lua_rawgeti(L, -1, p); + RageVector3 pos(0.0f, 0.0f, 0.0f); + if(!lua_istable(L, -1)) + { + LuaHelpers::ReportScriptErrorFmt("Position %zu is not a table.", p); + } + else + { +#define SET_POS_PART(i, part) \ + lua_rawgeti(L, -1, i); \ + pos.part= lua_tonumber(L, -1); \ + lua_pop(L, 1); + // If part of the position is not provided, we want it to + // default to zero, which lua_tonumber does. -Kyz + SET_POS_PART(1, x); + SET_POS_PART(2, y); + SET_POS_PART(3, z); +#undef SET_POS_PART + } + lua_pop(L, 1); + positions.push_back(pos); + } + } + } + lua_settop(L, 0); + LUA->Release(L); + } + } + for( unsigned c=0; cSetName( sName ); if( USE_ICON_METRICS ) - LOAD_ALL_COMMANDS_AND_SET_XY( m_vsprIcon[c] ); + { + if(positions_set_by_lua) + { + LOAD_ALL_COMMANDS(m_vsprIcon[c]); + m_vsprIcon[c]->SetXY(positions[c].x, positions[c].y); + m_vsprIcon[c]->SetZ(positions[c].z); + } + else + { + LOAD_ALL_COMMANDS_AND_SET_XY( m_vsprIcon[c] ); + } +#define OPTIONAL_COMMAND(onoff) \ + if(THEME->HasMetric(m_sName, "IconChoice" onoff "Command")) \ + { \ + m_vsprIcon[c]->AddCommand(onoff, THEME->GetMetricA(m_sName, "IconChoice" onoff "Command"), false); \ + } + OPTIONAL_COMMAND("On"); + OPTIONAL_COMMAND("Off"); +#undef OPTIONAL_COMMAND + } this->AddChild( m_vsprIcon[c] ); }