From a7a98a690e25cd7c0e7914ebd52ffb1bc1a59107 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sat, 5 Jul 2014 01:34:05 -0600 Subject: [PATCH 01/12] Replaced every use of lua_call with RunScriptOnStack. Modified RunScriptOnStack to report an error to log and system message using a passed in context string. Modified every use of RunScriptOnStack to avoid crashing so that the reported error can be seen. --- src/Actor.cpp | 5 +- src/ActorFrame.cpp | 13 ++- src/ActorUtil.cpp | 11 +-- src/DynamicActorScroller.cpp | 10 +- src/GameCommand.cpp | 10 +- src/LuaExpressionTransform.cpp | 3 +- src/LuaManager.cpp | 62 +++++++++--- src/LuaManager.h | 13 ++- src/MenuTimer.cpp | 6 +- src/NoteSkinManager.cpp | 5 +- src/OptionRowHandler.cpp | 15 ++- src/PercentageDisplay.cpp | 16 ++- src/Profile.cpp | 10 +- src/Screen.cpp | 7 +- src/ScreenTextEntry.cpp | 174 ++++++++++++++++----------------- src/ScreenWithMenuElements.cpp | 11 +-- src/ThemeMetric.h | 13 ++- src/UnlockManager.cpp | 3 +- 18 files changed, 219 insertions(+), 168 deletions(-) diff --git a/src/Actor.cpp b/src/Actor.cpp index 5ab0620f28..2883260c8d 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -1154,9 +1154,8 @@ void Actor::RunCommands( const LuaReference& cmds, const LuaReference *pParamTab pParamTable->PushSelf( L ); // call function with 2 arguments and 0 results - RString sError; - if( !LuaHelpers::RunScriptOnStack(L, sError, 2, 0) ) - LOG->Warn( "Error playing command: %s", sError.c_str() ); + RString Error= "Error playing command: "; + LuaHelpers::RunScriptOnStack(L, Error, 2, 0, true); LUA->Release(L); } diff --git a/src/ActorFrame.cpp b/src/ActorFrame.cpp index 3f17a94056..ac614d1fdd 100644 --- a/src/ActorFrame.cpp +++ b/src/ActorFrame.cpp @@ -242,9 +242,8 @@ void ActorFrame::DrawPrimitives() return; } this->PushSelf( L ); - RString sError; - if( !LuaHelpers::RunScriptOnStack(L, sError, 1, 0) ) // 1 arg, 0 results - LOG->Warn( "Error running DrawFunction: %s", sError.c_str() ); + RString Error= "Error running DrawFunction: "; + LuaHelpers::RunScriptOnStack(L, Error, 1, 0, true); // 1 arg, 0 results LUA->Release(L); return; } @@ -302,6 +301,8 @@ static int IdenticalChildrenSingleApplier(lua_State* L) lua_pushvalue(L, lua_upvalueindex(1)); // stack: table, obj, args, func lua_insert(L, 2); // stack: table, func, obj, args int args_count= lua_gettop(L) - 2; + // Not using RunScriptOnStack because we're inside a lua call already and + // we want an error to propagate up. lua_call(L, args_count, LUA_MULTRET); // stack: table, return_values return lua_gettop(L) - 1; } @@ -482,10 +483,8 @@ void ActorFrame::UpdateInternal( float fDeltaTime ) } this->PushSelf( L ); lua_pushnumber( L, fDeltaTime ); - RString sError; - - if( !LuaHelpers::RunScriptOnStack(L, sError, 2, 0) ) // 1 args, 0 results - LOG->Warn( "Error running m_UpdateFunction: %s", sError.c_str() ); + RString Error= "Error running UpdateFunction: "; + LuaHelpers::RunScriptOnStack(L, Error, 2, 0, true); // 1 args, 0 results LUA->Release(L); } } diff --git a/src/ActorUtil.cpp b/src/ActorUtil.cpp index 29046e9f19..fd98f53002 100644 --- a/src/ActorUtil.cpp +++ b/src/ActorUtil.cpp @@ -268,12 +268,11 @@ bool ActorUtil::LoadTableFromStackShowErrors( Lua *L ) lua_pushvalue( L, -1 ); func.SetFromStack( L ); - RString sError; - if( !LuaHelpers::RunScriptOnStack(L, sError, 0, 1) ) + RString Error= "Lua runtime error: "; + if( !LuaHelpers::RunScriptOnStack(L, Error, 0, 1, true) ) { lua_pop( L, 1 ); - sError = ssprintf( "Lua runtime error: %s", sError.c_str() ); - Dialog::OK( sError, "LUA_ERROR" ); + Dialog::OK( Error, "LUA_ERROR" ); return false; } @@ -285,9 +284,9 @@ bool ActorUtil::LoadTableFromStackShowErrors( Lua *L ) lua_Debug debug; lua_getinfo( L, ">nS", &debug ); - sError = ssprintf( "%s: must return a table", debug.short_src ); + Error = ssprintf( "%s: must return a table", debug.short_src ); - Dialog::OK( sError, "LUA_ERROR" ); + Dialog::OK( Error, "LUA_ERROR" ); return false; } return true; diff --git a/src/DynamicActorScroller.cpp b/src/DynamicActorScroller.cpp index ba877146de..2cf6ead5af 100644 --- a/src/DynamicActorScroller.cpp +++ b/src/DynamicActorScroller.cpp @@ -44,9 +44,8 @@ void DynamicActorScroller::LoadFromNode( const XNode *pNode ) lua_pushnil( L ); lua_pushnil( L ); - RString sError; - if( !LuaHelpers::RunScriptOnStack(L, sError, 2, 1) ) // 2 args, 1 result - LOG->Warn( "Error running LoadFunction: %s", sError.c_str() ); + RString Error= "Error running LoadFunction: "; + LuaHelpers::RunScriptOnStack(L, Error, 2, 1, true); // 2 args, 1 result m_iNumItems = (int) luaL_checknumber( L, -1 ); lua_pop( L, 1 ); @@ -119,9 +118,8 @@ void DynamicActorScroller::ConfigureActor( Actor *pActor, int iItem ) pActor->PushSelf( L ); LuaHelpers::Push( L, iItem ); - RString sError; - if( !LuaHelpers::RunScriptOnStack(L, sError, 2, 0) ) // 2 args, 0 results - LOG->Warn( "Error running LoadFunction: %s", sError.c_str() ); + RString Error= "Error running LoadFunction: "; + LuaHelpers::RunScriptOnStack(L, Error, 2, 0, true); // 2 args, 0 results LUA->Release(L); } diff --git a/src/GameCommand.cpp b/src/GameCommand.cpp index cf399ad2ef..43dde86c2d 100644 --- a/src/GameCommand.cpp +++ b/src/GameCommand.cpp @@ -235,7 +235,10 @@ void GameCommand::LoadOne( const Command& cmd ) else if( sName == "lua" ) { m_LuaFunction.SetFromExpression( sValue ); - ASSERT_M( !m_LuaFunction.IsNil(), ssprintf("\"%s\" evaluated to nil", sValue.c_str()) ); + if(m_LuaFunction.IsNil()) + { + LuaHelpers::ReportScriptError("Lua error in game command: \"" + sValue + "\" evaluated to nil"); + } } else if( sName == "screen" ) @@ -693,7 +696,7 @@ void GameCommand::ApplySelf( const vector &vpns ) const if( m_sStageModifiers != "" ) FOREACH_CONST( PlayerNumber, vpns, pn ) GAMESTATE->ApplyStageModifiers( *pn, m_sStageModifiers ); - if( m_LuaFunction.IsSet() ) + if( m_LuaFunction.IsSet() && !m_LuaFunction.IsNil() ) { Lua *L = LUA->Get(); FOREACH_CONST( PlayerNumber, vpns, pn ) @@ -702,7 +705,8 @@ void GameCommand::ApplySelf( const vector &vpns ) const ASSERT( !lua_isnil(L, -1) ); lua_pushnumber( L, *pn ); // 1st parameter - lua_call( L, 1, 0 ); // call function with 1 argument and 0 results + RString error= "Lua GameCommand error: "; + LuaHelpers::RunScriptOnStack(L, error, 1, 0, true); } LUA->Release(L); } diff --git a/src/LuaExpressionTransform.cpp b/src/LuaExpressionTransform.cpp index f8da8d936e..7ddeb91d56 100644 --- a/src/LuaExpressionTransform.cpp +++ b/src/LuaExpressionTransform.cpp @@ -26,7 +26,8 @@ void LuaExpressionTransform::TransformItemDirect( Actor &a, float fPositionOffse LuaHelpers::Push( L, fPositionOffsetFromCenter ); LuaHelpers::Push( L, iItemIndex ); LuaHelpers::Push( L, iNumItems ); - lua_call( L, 4, 0 ); // 4 args, 0 results + RString error= "Lua error in Transform function: "; + LuaHelpers::RunScriptOnStack(L, error, 4, 0, true); LUA->Release(L); } diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index 550109848f..ff8b9c0923 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -9,7 +9,9 @@ #include "arch/Dialog/Dialog.h" #include "XmlFile.h" #include "Command.h" +#include "RageLog.h" #include "RageTypes.h" +#include "ScreenManager.h" #include // conversion for lua functions. #include @@ -787,43 +789,75 @@ bool LuaHelpers::LoadScript( Lua *L, const RString &sScript, const RString &sNam return true; } -bool LuaHelpers::RunScriptOnStack( Lua *L, RString &sError, int iArgs, int iReturnValues ) +void LuaHelpers::ReportScriptError(RString const& Error) +{ + size_t line_break_pos= Error.find('\n'); + RString short_error; + if(line_break_pos == RString::npos) + { + short_error= Error; + } + else + { + short_error= Error.substr(0, line_break_pos); + } + SCREENMAN->SystemMessage(short_error); + LOG->Warn(Error.c_str()); +} + +bool LuaHelpers::RunScriptOnStack( Lua *L, RString &Error, int Args, int ReturnValues, bool ReportError ) { lua_pushcfunction( L, GetLuaStack ); // move the error function above the function and params - int iErrFunc = lua_gettop(L) - iArgs - 1; - lua_insert( L, iErrFunc ); + int ErrFunc = lua_gettop(L) - Args - 1; + lua_insert( L, ErrFunc ); // evaluate - int ret = lua_pcall( L, iArgs, iReturnValues, iErrFunc ); + int ret = lua_pcall( L, Args, ReturnValues, ErrFunc ); if( ret ) { - LuaHelpers::Pop( L, sError ); - lua_remove( L, iErrFunc ); - for( int i = 0; i < iReturnValues; ++i ) + if(ReportError) + { + RString lerror; + LuaHelpers::Pop( L, lerror ); + Error+= lerror; + ReportScriptError(Error); + } + else + { + LuaHelpers::Pop( L, Error ); + } + lua_remove( L, ErrFunc ); + for( int i = 0; i < ReturnValues; ++i ) lua_pushnil( L ); return false; } - lua_remove( L, iErrFunc ); + lua_remove( L, ErrFunc ); return true; } -bool LuaHelpers::RunScript( Lua *L, const RString &sScript, const RString &sName, RString &sError, int iArgs, int iReturnValues ) +bool LuaHelpers::RunScript( Lua *L, const RString &Script, const RString &Name, RString &Error, int Args, int ReturnValues, bool ReportError ) { - if( !LoadScript(L, sScript, sName, sError) ) + RString lerror; + if( !LoadScript(L, Script, Name, lerror) ) { - lua_pop( L, iArgs ); - for( int i = 0; i < iReturnValues; ++i ) + Error+= lerror; + if(ReportError) + { + ReportScriptError(Error); + } + lua_pop( L, Args ); + for( int i = 0; i < ReturnValues; ++i ) lua_pushnil( L ); return false; } // move the function above the params - lua_insert( L, lua_gettop(L) - iArgs ); + lua_insert( L, lua_gettop(L) - Args ); - return LuaHelpers::RunScriptOnStack( L, sError, iArgs, iReturnValues ); + return LuaHelpers::RunScriptOnStack( L, Error, Args, ReturnValues, ReportError ); } bool LuaHelpers::RunExpression( Lua *L, const RString &sExpression, const RString &sName ) diff --git a/src/LuaManager.h b/src/LuaManager.h index 1dce0a906b..19ce04a377 100644 --- a/src/LuaManager.h +++ b/src/LuaManager.h @@ -58,15 +58,22 @@ namespace LuaHelpers * and the stack is unchanged. */ bool LoadScript( Lua *L, const RString &sScript, const RString &sName, RString &sError ); + /* Report the error through the log and on the screen. */ + void ReportScriptError(RString const& Error); + /* Run the function with arguments at the top of the stack, with the given * number of arguments. The specified number of return values are left on * the Lua stack. On error, nils are left on the stack, sError is set and - * false is returned. */ - bool RunScriptOnStack( Lua *L, RString &sError, int iArgs = 0, int iReturnValues = 0 ); + * false is returned. + * If ReportError is true, Error should contain the string to prepend + * when reporting. The error is reported through LOG->Warn and + * SCREENMAN->SystemMessage. + */ + bool RunScriptOnStack( Lua *L, RString &Error, int Args = 0, int ReturnValues = 0, bool ReportError = false ); /* LoadScript the given script, and RunScriptOnStack it. * iArgs arguments are at the top of the stack. */ - bool RunScript( Lua *L, const RString &sScript, const RString &sName, RString &sError, int iArgs = 0, int iReturnValues = 0 ); + bool RunScript( Lua *L, const RString &Script, const RString &Name, RString &Error, int Args = 0, int ReturnValues = 0, bool ReportError = false ); /* Run the given expression, returning a single value, and leave the return * value on the stack. On error, push nil. */ diff --git a/src/MenuTimer.cpp b/src/MenuTimer.cpp index 066b593e29..5115544399 100644 --- a/src/MenuTimer.cpp +++ b/src/MenuTimer.cpp @@ -183,9 +183,9 @@ void MenuTimer::SetText( float fSeconds ) LuaHelpers::Push( L, fSeconds ); // call function with 1 argument and 1 result - RString sError; - if( !LuaHelpers::RunScriptOnStack(L, sError, 1, 1) ) - LOG->Warn( "Error running Text%iFormatFunction: %s", i+1, sError.c_str() ); + RString Error= "Error running Text" + (i+1); + Error+= "FormatFunction: "; + LuaHelpers::RunScriptOnStack(L, Error, 1, 1, true); RString sText; LuaHelpers::Pop( L, sText ); diff --git a/src/NoteSkinManager.cpp b/src/NoteSkinManager.cpp index a810dc6708..9194574f50 100644 --- a/src/NoteSkinManager.cpp +++ b/src/NoteSkinManager.cpp @@ -169,11 +169,10 @@ void NoteSkinManager::LoadNoteSkinDataRecursive( const RString &sNoteSkinName_, LOG->Trace( "Load script \"%s\"", sFile.c_str() ); Lua *L = LUA->Get(); - RString sError; + RString Error= "Error running " + sFile + ": "; refScript.PushSelf( L ); - if( !LuaHelpers::RunScript(L, sScript, "@" + sFile, sError, 1, 1) ) + if( !LuaHelpers::RunScript(L, sScript, "@" + sFile, Error, 1, 1, true) ) { - LOG->Trace( "Error running %s: %s", sFile.c_str(), sError.c_str() ); lua_pop( L, 1 ); } else diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index dcab098562..e24665cddc 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -877,7 +877,8 @@ public: return false; } m_pLuaTable->PushSelf( L ); - lua_call( L, 1, 1 ); // call function with 1 argument and 1 result + RString error= RowName + " \"EnabledForPlayers\": "; + LuaHelpers::RunScriptOnStack(L, error, 1, 1, true); if(!lua_istable(L, -1)) { LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" did not return a table.", RowName.c_str()); @@ -960,7 +961,8 @@ public: // Argument 1 (self): m_pLuaTable->PushSelf( L ); - lua_call( L, 1, 1 ); // call function with 1 argument and 1 result + RString error= "EnabledForPlayers: "; + LuaHelpers::RunScriptOnStack( L, error, 1, 1, true ); m_Def.m_vEnabledForPlayers.clear(); // and fill in with supplied PlayerNumbers below lua_pushnil( L ); @@ -1135,7 +1137,8 @@ public: 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 + RString error= "LoadSelections: "; + LuaHelpers::RunScriptOnStack( L, error, 3, 0, true ); ASSERT( lua_gettop(L) == 2 ); lua_pop( L, 1 ); // pop option table @@ -1190,7 +1193,8 @@ public: 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 + RString error= "SaveSelections: "; + LuaHelpers::RunScriptOnStack( L, error, 3, 0, true ); ASSERT( lua_gettop(L) == 2 ); lua_pop( L, 1 ); // pop option table @@ -1221,7 +1225,8 @@ public: 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); + RString error= "NotifyOfSelection: "; + LuaHelpers::RunScriptOnStack(L, error, 3, 1, true); if(lua_toboolean(L, -1)) { lua_pop(L, 1); diff --git a/src/PercentageDisplay.cpp b/src/PercentageDisplay.cpp index e932844e3f..28eec2fa7e 100644 --- a/src/PercentageDisplay.cpp +++ b/src/PercentageDisplay.cpp @@ -42,8 +42,15 @@ void PercentageDisplay::LoadFromNode( const XNode* pNode ) const XNode *pChild = pNode->GetChild( "Percent" ); if( pChild == NULL ) - RageException::Throw( "%s: PercentageDisplay: missing the node \"Percent\"", ActorUtil::GetWhere(pNode).c_str() ); - m_textPercent.LoadFromNode( pChild ); + { + LuaHelpers::ReportScriptError(ActorUtil::GetWhere(pNode) + ": PercentageDisplay: missing the node \"Percent\""); + // Make a BitmapText just so we don't crash. + m_textPercent.LoadFromFont(THEME->GetPathF("", "Common Normal")); + } + else + { + m_textPercent.LoadFromNode( pChild ); + } this->AddChild( &m_textPercent ); pChild = pNode->GetChild( "PercentRemainder" ); @@ -154,9 +161,8 @@ void PercentageDisplay::Refresh() m_FormatPercentScore.PushSelf( L ); ASSERT( !lua_isnil(L, -1) ); LuaHelpers::Push( L, fPercentDancePoints ); - RString sError; - if( !LuaHelpers::RunScriptOnStack(L, sError, 1, 1) ) // 1 arg, 1 result - LOG->Warn( "Error running FormatPercentScore: %s", sError.c_str() ); + RString Error= "Error running FormatPercentScore: "; + LuaHelpers::RunScriptOnStack(L, Error, 1, 1, true); // 1 arg, 1 result LuaHelpers::Pop( L, sNumToDisplay ); LUA->Release(L); diff --git a/src/Profile.cpp b/src/Profile.cpp index c6f72606a2..c8b17cd1af 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -846,9 +846,8 @@ void Profile::LoadCustomFunction( RString sDir ) LuaHelpers::Push(L, sDir); // Run it - RString sError; - if (!LuaHelpers::RunScriptOnStack(L, sError, 2, 0)) - LOG->Warn("Error running CustomLoadFunction: %s", sError.c_str()); + RString Error= "Error running CustomLoadFunction: "; + LuaHelpers::RunScriptOnStack(L, Error, 2, 0, true); LUA->Release(L); } @@ -1029,9 +1028,8 @@ bool Profile::SaveAllToDir( RString sDir, bool bSignData ) const LuaHelpers::Push(L, sDir); // Run it - RString sError; - if (!LuaHelpers::RunScriptOnStack(L, sError, 2, 0)) - LOG->Warn("Error running CustomSaveFunction: %s", sError.c_str()); + RString Error= "Error running CustomSaveFunction: "; + LuaHelpers::RunScriptOnStack(L, Error, 2, 0, true); LUA->Release(L); diff --git a/src/Screen.cpp b/src/Screen.cpp index 758c163732..7e4c0b4f84 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -337,11 +337,8 @@ bool Screen::PassInputToLua(const InputEventPlus& input) { callback->second.PushSelf(L); lua_pushvalue(L, -2); - RString error; - if(!LuaHelpers::RunScriptOnStack(L, error, 1, 1)) - { - LOG->Warn("Error running input callback: %s", error.c_str()); - } + RString error= "Error running input callback: "; + LuaHelpers::RunScriptOnStack(L, error, 1, 1, true); handled= lua_toboolean(L, -1); lua_pop(L, 1); } diff --git a/src/ScreenTextEntry.cpp b/src/ScreenTextEntry.cpp index 9cfd7e5233..79c37c8b37 100644 --- a/src/ScreenTextEntry.cpp +++ b/src/ScreenTextEntry.cpp @@ -376,7 +376,10 @@ void ScreenTextEntry::TextEntrySettings::FromStack( lua_State *L ) lua_getfield( L, iTab, "Question" ); pStr = lua_tostring( L, -1 ); if( pStr == NULL ) - RageException::Throw( "\"Question\" entry is not a string." ); + { + LuaHelpers::ReportScriptError("ScreenTextEntry \"Question\" entry is not a string."); + pStr= ""; + } sQuestion = pStr; lua_settop( L, iTab ); @@ -398,53 +401,34 @@ void ScreenTextEntry::TextEntrySettings::FromStack( lua_State *L ) bPassword = !!lua_toboolean( L, -1 ); lua_settop( L, iTab ); - // and now the hard part, the functions. - // Validate - lua_getfield( L, iTab, "Validate" ); - if( !lua_isfunction( L, -1 ) && !lua_isnil( L, -1 ) ) - RageException::Throw( "\"Validate\" is not a function." ); - Validate.SetFromStack( L ); - lua_settop( L, iTab ); +#define SET_FUNCTION_MEMBER(memname) \ + lua_getfield(L, iTab, #memname); \ + if(lua_isfunction(L, -1)) \ + { \ + memname.SetFromStack(L); \ + } \ + else if(!lua_isnil(L, -1)) \ + { \ + LuaHelpers::ReportScriptError("ScreenTextEntry \"" #memname "\" is not a function."); \ + } \ + lua_settop(L, iTab); - // OnOK - lua_getfield( L, iTab, "OnOK" ); - if( !lua_isfunction( L, -1 ) && !lua_isnil( L, -1 ) ) - RageException::Throw( "\"OnOK\" is not a function." ); - OnOK.SetFromStack( L ); - lua_settop( L, iTab ); - - // OnCancel - lua_getfield( L, iTab, "OnCancel" ); - if( !lua_isfunction( L, -1 ) && !lua_isnil( L, -1 ) ) - RageException::Throw( "\"OnCancel\" is not a function." ); - OnCancel.SetFromStack( L ); - lua_settop( L, iTab ); - - // ValidateAppend - lua_getfield( L, iTab, "ValidateAppend" ); - if( !lua_isfunction( L, -1 ) && !lua_isnil( L, -1 ) ) - RageException::Throw( "\"ValidateAppend\" is not a function." ); - ValidateAppend.SetFromStack( L ); - lua_settop( L, iTab ); - - // FormatAnswerForDisplay - lua_getfield( L, iTab, "FormatAnswerForDisplay" ); - if( !lua_isfunction( L, -1 ) && !lua_isnil( L, -1 ) ) - RageException::Throw( "\"FormatAnswerForDisplay\" is not a function." ); - FormatAnswerForDisplay.SetFromStack( L ); - lua_settop( L, iTab ); + SET_FUNCTION_MEMBER(Validate); + SET_FUNCTION_MEMBER(OnOK); + SET_FUNCTION_MEMBER(OnCancel); + SET_FUNCTION_MEMBER(ValidateAppend); + SET_FUNCTION_MEMBER(FormatAnswerForDisplay); +#undef SET_FUNCTION_MEMBER } // Lua bridges static bool ValidateFromLua( const RString &sAnswer, RString &sErrorOut ) { - Lua *L = LUA->Get(); - - if( g_ValidateFunc.IsNil() ) + if(g_ValidateFunc.IsNil() || !g_ValidateFunc.IsSet()) { - LUA->Release(L); return true; } + Lua *L = LUA->Get(); g_ValidateFunc.PushSelf( L ); @@ -454,69 +438,70 @@ static bool ValidateFromLua( const RString &sAnswer, RString &sErrorOut ) // Argument 2 (error out): lua_pushstring( L, sErrorOut ); - lua_call( L, 2, 2 ); // call function with 2 arguments and 2 results - - if( !lua_isstring(L, -1) ) - RageException::Throw( "\"Validate\" did not return a string." ); - - if( !lua_isboolean(L, -2) ) - RageException::Throw( "\"Validate\" did not return a boolean." ); - - RString sErrorFromLua; - LuaHelpers::Pop( L, sErrorFromLua ); - if( !sErrorFromLua.empty() ) - sErrorOut = sErrorFromLua; - - bool bValidate; - LuaHelpers::Pop( L, bValidate ); + bool valid= false; + RString error= "Lua error in ScreenTextEntry Validate: "; + if(LuaHelpers::RunScriptOnStack(L, error, 2, 2, true)) + { + if(!lua_isstring(L, -1) || !lua_isboolean(L, -2)) + { + LuaHelpers::ReportScriptError("Lua error: ScreenTextEntry Validate did not return 'bool, string'."); + } + else + { + RString ErrorFromLua; + LuaHelpers::Pop( L, ErrorFromLua ); + if( !ErrorFromLua.empty() ) + { + sErrorOut = ErrorFromLua; + } + LuaHelpers::Pop( L, valid ); + } + } + lua_settop(L, 0); LUA->Release(L); - return bValidate; + return valid; } static void OnOKFromLua( const RString &sAnswer ) { - Lua *L = LUA->Get(); - - if( g_OnOKFunc.IsNil() ) + if(g_OnOKFunc.IsNil() || !g_OnOKFunc.IsSet()) { - LUA->Release(L); return; } + Lua *L = LUA->Get(); g_OnOKFunc.PushSelf( L ); // Argument 1 (answer): lua_pushstring( L, sAnswer ); - lua_call( L, 1, 0 ); // call function with 1 argument and 0 results + RString error= "Lua error in ScreenTextEntry OnOK: "; + LuaHelpers::RunScriptOnStack(L, error, 1, 0, true); LUA->Release(L); } static void OnCancelFromLua() { - Lua *L = LUA->Get(); - - if( g_OnCancelFunc.IsNil() ) + if(g_OnCancelFunc.IsNil() || !g_OnCancelFunc.IsSet()) { - LUA->Release(L); return; } + Lua *L = LUA->Get(); g_OnCancelFunc.PushSelf( L ); - lua_call( L, 0, 0 ); // call function with 0 arguments and 0 results + RString error= "Lua error in ScreenTextEntry OnCancel: "; + LuaHelpers::RunScriptOnStack(L, error, 0, 0, true); LUA->Release(L); } static bool ValidateAppendFromLua( const RString &sAnswerBeforeChar, RString &sAppend ) { - Lua *L = LUA->Get(); - - if( g_ValidateAppendFunc.IsNil() ) + if(g_ValidateAppendFunc.IsNil() || !g_ValidateAppendFunc.IsSet()) { - LUA->Release(L); return true; } + Lua *L = LUA->Get(); g_ValidateAppendFunc.PushSelf( L ); @@ -525,41 +510,54 @@ static bool ValidateAppendFromLua( const RString &sAnswerBeforeChar, RString &sA // Argument 2 (Append): lua_pushstring( L, sAppend ); - lua_call( L, 2, 1 ); // call function with 2 arguments and 1 result - if( !lua_isboolean(L, -1) ) - RageException::Throw( "\"ValidateAppend\" did not return a boolean." ); - - bool bAppend; - LuaHelpers::Pop( L, bAppend ); + bool append= false; + RString error= "Lua error in ScreenTextEntry ValidateAppend: "; + if(LuaHelpers::RunScriptOnStack(L, error, 2, 1, true)) + { + if( !lua_isboolean(L, -1) ) + { + LuaHelpers::ReportScriptError("\"ValidateAppend\" did not return a boolean."); + } + else + { + LuaHelpers::Pop( L, append ); + } + } + lua_settop(L, 0); LUA->Release(L); - return bAppend; + return append; } static RString FormatAnswerForDisplayFromLua( const RString &sAnswer ) { - Lua *L = LUA->Get(); - - if( g_FormatAnswerForDisplayFunc.IsNil() ) + if(g_FormatAnswerForDisplayFunc.IsNil() || !g_FormatAnswerForDisplayFunc.IsSet()) { - LUA->Release(L); return sAnswer; } + Lua *L = LUA->Get(); g_FormatAnswerForDisplayFunc.PushSelf( L ); // Argument 1 (Answer): lua_pushstring( L, sAnswer ); - lua_call( L, 1, 1 ); // call function with 1 argument and 1 result - - if( !lua_isstring(L, -1) ) - RageException::Throw( "\"FormatAnswerForDisplay\" did not return a string." ); - - RString sAnswerFromLua; - LuaHelpers::Pop( L, sAnswerFromLua ); + RString answer; + RString error= "Lua error in ScreenTextEntry FormatAnswerForDisplay: "; + if(LuaHelpers::RunScriptOnStack(L, error, 1, 1, true)) + { + if( !lua_isstring(L, -1) ) + { + LuaHelpers::ReportScriptError("\"FormatAnswerForDisplay\" did not return a string."); + } + else + { + LuaHelpers::Pop(L, answer); + } + } + lua_settop(L, 0); LUA->Release(L); - return sAnswerFromLua; + return answer; } void ScreenTextEntry::LoadFromTextEntrySettings( const TextEntrySettings &settings ) diff --git a/src/ScreenWithMenuElements.cpp b/src/ScreenWithMenuElements.cpp index 78f99d77c1..e9304ac070 100644 --- a/src/ScreenWithMenuElements.cpp +++ b/src/ScreenWithMenuElements.cpp @@ -194,17 +194,16 @@ void ScreenWithMenuElements::StartPlayingMusic() */ if( ft == FT_Lua ) { - RString sScript; - RString sError; - if( GetFileContents(m_sPathToMusic, sScript) ) + RString Script; + RString Error= "Lua runtime error: "; + if( GetFileContents(m_sPathToMusic, Script) ) { Lua *L = LUA->Get(); - if( !LuaHelpers::RunScript(L, sScript, "@"+m_sPathToMusic, sError, 0, 1) ) + if( !LuaHelpers::RunScript(L, Script, "@"+m_sPathToMusic, Error, 0, 1, true) ) { LUA->Release( L ); - sError = ssprintf( "Lua runtime error: %s", sError.c_str() ); - Dialog::OK( sError, "LUA_ERROR" ); + Dialog::OK( Error, "LUA_ERROR" ); return; } else diff --git a/src/ThemeMetric.h b/src/ThemeMetric.h index a4073ffa1a..41ac59667e 100644 --- a/src/ThemeMetric.h +++ b/src/ThemeMetric.h @@ -146,9 +146,16 @@ public: // call function with 0 arguments and 1 result m_Value.PushSelf( L ); - lua_call(L, 0, 1); - ASSERT( !lua_isnil(L, -1) ); - LuaHelpers::Pop( L, m_currentValue ); + RString error= m_sGroup + ": " + m_sName + ": "; + LuaHelpers::RunScriptOnStack(L, error, 0, 1, true); + if(!lua_isnil(L, -1)) + { + LuaHelpers::Pop( L, m_currentValue ); + } + else + { + lua_pop(L, 1); + } LUA->Release(L); } diff --git a/src/UnlockManager.cpp b/src/UnlockManager.cpp index cba162190d..ae77104499 100644 --- a/src/UnlockManager.cpp +++ b/src/UnlockManager.cpp @@ -502,7 +502,8 @@ void UnlockManager::Load() current.PushSelf( L ); // call function with 1 argument and 0 results - lua_call( L, 1, 0 ); + RString error= "Lua error in command: "; + LuaHelpers::RunScriptOnStack(L, error, 1, 0, true); if( current.m_bRoulette ) m_RouletteCodes.insert( current.m_sEntryID ); From ff1392cdba21737c1b01a4e86cc1cd72c3d7e2e1 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sat, 5 Jul 2014 14:14:23 -0600 Subject: [PATCH 02/12] Made Screen not go to next screen if the screen name is empty. --- src/Screen.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Screen.cpp b/src/Screen.cpp index 7e4c0b4f84..3568fa0f9d 100644 --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -234,7 +234,17 @@ void Screen::HandleScreenMessage( const ScreenMessage SM ) if( SCREENMAN->IsStackedScreen(this) ) SCREENMAN->PopTopScreen( m_smSendOnPop ); else - SCREENMAN->SetNewScreen( SM == SM_GoToNextScreen? GetNextScreenName():GetPrevScreen() ); + { + RString ToScreen= (SM == SM_GoToNextScreen? GetNextScreenName():GetPrevScreen()); + if(ToScreen == "") + { + LuaHelpers::ReportScriptError("Error: Tried to go to empty screen."); + } + else + { + SCREENMAN->SetNewScreen(ToScreen); + } + } } else if( SM == SM_GainFocus ) { From 026f968fa44d72fafc05212d622307a0ef6cd589 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Mon, 7 Jul 2014 00:39:27 -0600 Subject: [PATCH 03/12] Changed many uses of Dialog::OK and LOG->Warn to use ReportScriptError. --- src/Actor.cpp | 30 +++++++++++++----------------- src/ActorFrame.cpp | 6 +++--- src/ActorMultiVertex.cpp | 20 ++++++++++---------- src/ActorUtil.cpp | 5 ++--- src/Background.cpp | 8 ++++---- src/BitmapText.cpp | 2 +- src/ComboGraph.cpp | 2 +- src/CommonMetrics.cpp | 2 +- src/DifficultyIcon.cpp | 3 +-- src/EnumHelper.cpp | 2 +- src/GameCommand.cpp | 4 +--- src/GameState.cpp | 6 +++--- src/LuaManager.cpp | 19 ++++++++++++++----- src/LuaManager.h | 4 +++- src/LyricsLoader.cpp | 4 ++-- src/MusicWheel.cpp | 2 +- src/NoteSkinManager.cpp | 2 +- src/OptionRowHandler.cpp | 30 +++++++++++++++--------------- src/PrefsManager.cpp | 6 +++--- src/Profile.cpp | 10 +++++----- src/ScreenOptions.cpp | 2 +- src/ScreenOptionsMaster.cpp | 12 +++++++----- src/ScreenSMOnlineLogin.cpp | 2 +- src/ScreenSelectMaster.cpp | 2 +- src/ScreenWithMenuElements.cpp | 2 -- src/SongManager.cpp | 12 ++++++------ src/Sprite.cpp | 3 +-- src/Style.cpp | 4 ++-- src/TitleSubstitution.cpp | 3 ++- src/UnlockManager.cpp | 16 ++++++++-------- src/XmlFileUtil.cpp | 12 +++++------- 31 files changed, 119 insertions(+), 118 deletions(-) diff --git a/src/Actor.cpp b/src/Actor.cpp index 2883260c8d..f3e580dce1 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -4,7 +4,6 @@ #include "RageUtil.h" #include "RageMath.h" #include "RageLog.h" -#include "arch/Dialog/Dialog.h" #include "Foreach.h" #include "XmlFile.h" #include "LuaBinding.h" @@ -793,10 +792,7 @@ void Actor::BeginTweening( float time, ITween *pTween ) // recursing ActorCommand. if( m_Tweens.size() > 50 ) { - RString sError = ssprintf( "Tween overflow: \"%s\"; infinitely recursing ActorCommand?", GetLineage().c_str() ); - - LOG->Warn( "%s", sError.c_str() ); - Dialog::OK( sError ); + LuaHelpers::ReportScriptErrorFmt("Tween overflow: \"%s\"; infinitely recursing ActorCommand?", GetLineage().c_str()); this->FinishTweening(); } @@ -1129,7 +1125,7 @@ void Actor::RunCommands( const LuaReference& cmds, const LuaReference *pParamTab { if( !cmds.IsSet() || cmds.IsNil() ) { - LOG->Warn( "RunCommands: command is unset or nil" ); + LuaHelpers::ReportScriptError("RunCommands: command is unset or nil"); return; } @@ -1139,7 +1135,7 @@ void Actor::RunCommands( const LuaReference& cmds, const LuaReference *pParamTab cmds.PushSelf( L ); if( lua_isnil(L, -1) ) { - LOG->Warn("Error compiling commands"); + LuaHelpers::ReportScriptError("Error compiling commands"); LUA->Release(L); return; } @@ -1300,7 +1296,7 @@ void Actor::AddCommand( const RString &sCmdName, apActorCommands apac ) if( HasCommand(sCmdName) ) { RString sWarning = GetLineage()+"'s command '"+sCmdName+"' defined twice"; - Dialog::OK( sWarning, "COMMAND_DEFINED_TWICE" ); + LuaHelpers::ReportScriptError(sWarning, "COMMAND_DEFINED_TWICE"); } RString sMessage; @@ -1403,7 +1399,7 @@ public: float fTime = FArg(1); if (fTime < 0) { - LOG->Warn("Lua: sleep(%f): time must not be negative", fTime); + LuaHelpers::ReportScriptErrorFmt("Lua: sleep(%f): time must not be negative", fTime); return 0; } p->Sleep(fTime); @@ -1414,7 +1410,7 @@ public: float fTime = FArg(1); if (fTime < 0) { - LOG->Warn("Lua: linear(%f): tween time must not be negative", fTime); + LuaHelpers::ReportScriptErrorFmt("Lua: linear(%f): tween time must not be negative", fTime); return 0; } p->BeginTweening(fTime, TWEEN_LINEAR); @@ -1425,7 +1421,7 @@ public: float fTime = FArg(1); if (fTime < 0) { - LOG->Warn("Lua: accelerate(%f): tween time must not be negative", fTime); + LuaHelpers::ReportScriptErrorFmt("Lua: accelerate(%f): tween time must not be negative", fTime); return 0; } p->BeginTweening(fTime, TWEEN_ACCELERATE); @@ -1436,7 +1432,7 @@ public: float fTime = FArg(1); if (fTime < 0) { - LOG->Warn("Lua: decelerate(%f): tween time must not be negative", fTime); + LuaHelpers::ReportScriptErrorFmt("Lua: decelerate(%f): tween time must not be negative", fTime); return 0; } p->BeginTweening(fTime, TWEEN_DECELERATE); @@ -1447,7 +1443,7 @@ public: float fTime = FArg(1); if (fTime < 0) { - LOG->Warn("Lua: spring(%f): tween time must not be negative", fTime); + LuaHelpers::ReportScriptErrorFmt("Lua: spring(%f): tween time must not be negative", fTime); return 0; } p->BeginTweening(fTime, TWEEN_SPRING); @@ -1458,7 +1454,7 @@ public: float fTime = FArg(1); if (fTime < 0) { - LOG->Warn("Lua: tween(%f): tween time must not be negative", fTime); + LuaHelpers::ReportScriptErrorFmt("Lua: tween(%f): tween time must not be negative", fTime); return 0; } ITween *pTween = ITween::CreateFromStack( L, 2 ); @@ -1558,7 +1554,7 @@ public: float fPeriod = FArg(1); if (fPeriod <= 0) { - LOG->Warn("Effect period (%f) must be positive; ignoring", fPeriod); + LuaHelpers::ReportScriptErrorFmt("Effect period (%f) must be positive; ignoring", fPeriod); return 0; } p->SetEffectPeriod(FArg(1)); @@ -1569,13 +1565,13 @@ public: float f1 = FArg(1), f2 = FArg(2), f3 = FArg(3), f4 = FArg(4); if (f1 < 0 || f2 < 0 || f3 < 0 || f4 < 0) { - LOG->Warn("Effect timings (%f,%f,%f,%f) must not be negative; ignoring", + LuaHelpers::ReportScriptErrorFmt("Effect timings (%f,%f,%f,%f) must not be negative; ignoring", f1, f2, f3, f4); return 0; } if (f1 == 0 && f2 == 0 && f3 == 0 && f4 == 0) { - LOG->Warn("Effect timings (0,0,0,0) must not all be zero; ignoring"); + LuaHelpers::ReportScriptErrorFmt("Effect timings (0,0,0,0) must not all be zero; ignoring"); return 0; } p->SetEffectTiming(FArg(1), FArg(2), FArg(3), FArg(4)); diff --git a/src/ActorFrame.cpp b/src/ActorFrame.cpp index ac614d1fdd..845acd41eb 100644 --- a/src/ActorFrame.cpp +++ b/src/ActorFrame.cpp @@ -224,7 +224,7 @@ void ActorFrame::DrawPrimitives() { if( m_bClearZBuffer ) { - LOG->Warn( "ClearZBuffer not supported on ActorFrames" ); + LuaHelpers::ReportScriptErrorFmt( "ClearZBuffer not supported on ActorFrames" ); m_bClearZBuffer = false; } @@ -238,7 +238,7 @@ void ActorFrame::DrawPrimitives() m_DrawFunction.PushSelf( L ); if( lua_isnil(L, -1) ) { - LOG->Warn( "Error compiling DrawFunction" ); + LuaHelpers::ReportScriptErrorFmt( "Error compiling DrawFunction" ); return; } this->PushSelf( L ); @@ -478,7 +478,7 @@ void ActorFrame::UpdateInternal( float fDeltaTime ) m_UpdateFunction.PushSelf( L ); if( lua_isnil(L, -1) ) { - LOG->Warn( "Error compiling UpdateFunction" ); + LuaHelpers::ReportScriptErrorFmt( "Error compiling UpdateFunction" ); return; } this->PushSelf( L ); diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index 879b5f7a2a..4d1f932b45 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -358,13 +358,13 @@ void ActorMultiVertex::AMV_TweenState::SetDrawState( DrawMode dm, int first, int { if(first >= (int)vertices.size() && vertices.size() > 0) { - LOG->Warn("ActorMultiVertex:SetDrawState: FirstToDraw > vertices.size(), %d > %u", FirstToDraw + 1, (unsigned int)vertices.size() ); + LuaHelpers::ReportScriptErrorFmt("ActorMultiVertex:SetDrawState: FirstToDraw > vertices.size(), %d > %u", FirstToDraw + 1, (unsigned int)vertices.size() ); return; } int safe_num= GetSafeNumToDraw( dm, num ); if( num != safe_num && num != -1 ) { - LOG->Warn("ActorMultiVertex:SetDrawState: NumToDraw %d is not valid for %u vertices with DrawMode %s", num, (unsigned int)vertices.size(), DrawModeNames[dm] ); + LuaHelpers::ReportScriptErrorFmt("ActorMultiVertex:SetDrawState: NumToDraw %d is not valid for %u vertices with DrawMode %s", num, (unsigned int)vertices.size(), DrawModeNames[dm] ); return; } _DrawMode= dm; @@ -416,7 +416,7 @@ public: // Use the number of arguments to determine which property a table is for if(lua_type(L, DataStackIndex) != LUA_TTABLE) { - LOG->Warn("ActorMultiVertex::SetVertex: non-table parameter supplied. Table of tables of vertex data expected."); + LuaHelpers::ReportScriptErrorFmt("ActorMultiVertex::SetVertex: non-table parameter supplied. Table of tables of vertex data expected."); return; } size_t NumDataParts = lua_objlen(L, DataStackIndex); @@ -428,7 +428,7 @@ public: size_t DataPieceElements = lua_objlen(L, DataPieceIndex); if(lua_type(L, DataPieceIndex) != LUA_TTABLE) { - LOG->Warn( "ActorMultiVertex::SetVertex: non-table parameter %u supplied inside table of parameters, table expected.", (unsigned int)i ); + LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: non-table parameter %u supplied inside table of parameters, table expected.", (unsigned int)i ); return; } int pushes = 1; @@ -462,7 +462,7 @@ public: } else { - LOG->Warn( "ActorMultiVertex::SetVertex: Parameter %u has %u elements supplied. 2, 3, or 4 expected.", (unsigned int)i, (unsigned int)DataPieceElements ); + LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: Parameter %u has %u elements supplied. 2, 3, or 4 expected.", (unsigned int)i, (unsigned int)DataPieceElements ); } // Avoid a stack underflow by only popping the amount we pushed. @@ -477,7 +477,7 @@ public: int Index = IArg(1)-1; if( Index < 0 ) { - LOG->Warn( "ActorMultiVertex::SetVertex: index %d provided, cannot set Index < 1", Index+1 ); + LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: index %d provided, cannot set Index < 1", Index+1 ); return 0; } else if( Index == (int) p->GetNumVertices() ) @@ -486,7 +486,7 @@ public: } else if( Index > (int) p->GetNumVertices() ) { - LOG->Warn( "ActorMultiVertex::SetVertex: Cannot set vertex %d if there is no vertex %d, only %u vertices.", Index+1 , Index, (unsigned int)p->GetNumVertices() ); + LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: Cannot set vertex %d if there is no vertex %d, only %u vertices.", Index+1 , Index, (unsigned int)p->GetNumVertices() ); return 0; } SetVertexFromStack(p, L, Index, lua_gettop(L)); @@ -504,7 +504,7 @@ public: First = IArg(1)-1; if( First < 0 ) { - LOG->Warn( "ActorMultiVertex::SetVertices: index %d provided, cannot set Index < 1", First+1 ); + LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertices: index %d provided, cannot set Index < 1", First+1 ); return 0; } } @@ -542,7 +542,7 @@ public: float Width = FArg(1); if( Width < 0 ) { - LOG->Warn( "ActorMultiVertex::SetLineWidth: cannot set negative width." ); + LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetLineWidth: cannot set negative width." ); return 0; } p->SetLineWidth(Width); @@ -557,7 +557,7 @@ public: int ArgsIndex= 1; if( !lua_istable(L, ArgsIndex) ) { - LOG->Warn( "ActorMultiVertex:SetDrawState: Table expected, something else recieved. Doing nothing."); + LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex:SetDrawState: Table expected, something else recieved. Doing nothing."); return 0; } // Fetch the draw mode, if provided. diff --git a/src/ActorUtil.cpp b/src/ActorUtil.cpp index fd98f53002..a8a50dd550 100644 --- a/src/ActorUtil.cpp +++ b/src/ActorUtil.cpp @@ -246,7 +246,7 @@ namespace { LUA->Release( L ); sError = ssprintf( "Lua runtime error: %s", sError.c_str() ); - Dialog::OK( sError, "LUA_ERROR" ); + LuaHelpers::ReportScriptError(sError); return NULL; } @@ -272,7 +272,6 @@ bool ActorUtil::LoadTableFromStackShowErrors( Lua *L ) if( !LuaHelpers::RunScriptOnStack(L, Error, 0, 1, true) ) { lua_pop( L, 1 ); - Dialog::OK( Error, "LUA_ERROR" ); return false; } @@ -286,7 +285,7 @@ bool ActorUtil::LoadTableFromStackShowErrors( Lua *L ) Error = ssprintf( "%s: must return a table", debug.short_src ); - Dialog::OK( Error, "LUA_ERROR" ); + LuaHelpers::ReportScriptError(Error, "LUA_ERROR"); return false; } return true; diff --git a/src/Background.cpp b/src/Background.cpp index deddf700f9..910d5b4284 100644 --- a/src/Background.cpp +++ b/src/Background.cpp @@ -328,7 +328,7 @@ bool BackgroundImpl::Layer::CreateBackground( const Song *pSong, const Backgroun switch( ft ) { default: - LOG->Warn( "CreateBackground() Unknown file type '%s'", vsResolved[0].c_str() ); + LuaHelpers::ReportScriptErrorFmt( "CreateBackground() Unknown file type '%s'", vsResolved[0].c_str() ); // fall through case FT_Bitmap: case FT_Sprite: @@ -364,12 +364,12 @@ bool BackgroundImpl::Layer::CreateBackground( const Song *pSong, const Backgroun BackgroundUtil::GetBackgroundEffects( sEffect, vsPaths, vsThrowAway ); if( vsPaths.empty() ) { - LOG->Warn( "BackgroundEffect '%s' is missing.",sEffect.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "BackgroundEffect '%s' is missing.",sEffect.c_str() ); sEffect = SBE_Centered; } else if( vsPaths.size() > 1 ) { - LOG->Warn( "BackgroundEffect '%s' has more than one match.",sEffect.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "BackgroundEffect '%s' has more than one match.",sEffect.c_str() ); sEffect = SBE_Centered; } else @@ -744,7 +744,7 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus XNode *pNode = change.m_def.CreateNode(); RString xml = XmlFileUtil::GetXML( pNode ); Trim( xml ); - LOG->Warn( "Tried to switch to a background that was never loaded:\n%s", xml.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Tried to switch to a background that was never loaded:\n%s", xml.c_str() ); SAFE_DELETE( pNode ); return; } diff --git a/src/BitmapText.cpp b/src/BitmapText.cpp index c15cf2b57a..95ca19f83a 100644 --- a/src/BitmapText.cpp +++ b/src/BitmapText.cpp @@ -132,7 +132,7 @@ void BitmapText::LoadFromNode( const XNode* pNode ) if( !pNode->GetAttrValue("Font", sFont) && !pNode->GetAttrValue("File", sFont) ) // accept "File" for backward compatibility { - LOG->Warn( "%s: BitmapText: Font or File attribute not found", + LuaHelpers::ReportScriptErrorFmt( "%s: BitmapText: Font or File attribute not found", ActorUtil::GetWhere(pNode).c_str() ); sFont = "Common Normal"; } diff --git a/src/ComboGraph.cpp b/src/ComboGraph.cpp index e582f2a9fb..e562d269b0 100644 --- a/src/ComboGraph.cpp +++ b/src/ComboGraph.cpp @@ -61,7 +61,7 @@ void ComboGraph::Load( RString sMetricsGroup ) if( m_pComboNumber != NULL ) this->AddChild( m_pComboNumber ); else - LOG->Warn( "ComboGraph: \"sMetricsGroup\" \"ComboNumber\" must be a BitmapText" ); + LuaHelpers::ReportScriptErrorFmt( "ComboGraph: \"sMetricsGroup\" \"ComboNumber\" must be a BitmapText" ); } } diff --git a/src/CommonMetrics.cpp b/src/CommonMetrics.cpp index b699738e03..3635dd72a1 100644 --- a/src/CommonMetrics.cpp +++ b/src/CommonMetrics.cpp @@ -92,7 +92,7 @@ static void RemoveStepsTypes( vector& inout, RString sStepsTypesToRem StepsType st = GAMEMAN->StringToStepsType(*i); if( st == StepsType_Invalid ) { - LOG->Warn( "Invalid StepsType value '%s' in '%s'", i->c_str(), sStepsTypesToRemove.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Invalid StepsType value '%s' in '%s'", i->c_str(), sStepsTypesToRemove.c_str() ); continue; } diff --git a/src/DifficultyIcon.cpp b/src/DifficultyIcon.cpp index 26f30287d7..744f49e591 100644 --- a/src/DifficultyIcon.cpp +++ b/src/DifficultyIcon.cpp @@ -6,7 +6,6 @@ #include "Steps.h" #include "GameState.h" #include "RageDisplay.h" -#include "arch/Dialog/Dialog.h" #include "Trail.h" #include "ActorUtil.h" #include "XmlFile.h" @@ -35,7 +34,7 @@ bool DifficultyIcon::Load( RString sPath ) NUM_Difficulty, NUM_Difficulty*2, iStates ); - Dialog::OK( sError ); + LuaHelpers::ReportScriptError(sError); } StopAnimating(); return true; diff --git a/src/EnumHelper.cpp b/src/EnumHelper.cpp index cc9d274f82..921a92a935 100644 --- a/src/EnumHelper.cpp +++ b/src/EnumHelper.cpp @@ -70,7 +70,7 @@ int CheckEnum( lua_State *L, LuaReference &table, int iPos, int iInvalid, const { RString errmsg; LuaHelpers::Pop(L, errmsg); - LOG->Warn(errmsg.c_str()); + LuaHelpers::ReportScriptError(errmsg); lua_pop(L, 2); return iInvalid; } diff --git a/src/GameCommand.cpp b/src/GameCommand.cpp index 43dde86c2d..dbda6baa2b 100644 --- a/src/GameCommand.cpp +++ b/src/GameCommand.cpp @@ -14,7 +14,6 @@ #include "Game.h" #include "Style.h" #include "Foreach.h" -#include "arch/Dialog/Dialog.h" #include "GameSoundManager.h" #include "PlayerState.h" #include "SongManager.h" @@ -426,8 +425,7 @@ void GameCommand::LoadOne( const Command& cmd ) else { RString sWarning = ssprintf( "Command '%s' is not valid.", cmd.GetOriginalCommandString().c_str() ); - LOG->Warn( "%s", sWarning.c_str() ); - Dialog::OK( sWarning, "INVALID_GAME_COMMAND" ); + LuaHelpers::ReportScriptError(sWarning, "INVALID_GAME_COMMAND"); } } diff --git a/src/GameState.cpp b/src/GameState.cpp index 57a58f8377..ba4da3ce06 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -1879,7 +1879,7 @@ void GameState::StoreRankingName( PlayerNumber pn, RString sName ) { if( file.GetLine(sLine) == -1 ) { - LOG->Warn( "Error reading \"%s\": %s", NAME_BLACKLIST_FILE, file.GetError().c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Error reading \"%s\": %s", NAME_BLACKLIST_FILE, file.GetError().c_str() ); break; } @@ -2085,7 +2085,7 @@ Difficulty GameState::GetEasiestStepsDifficulty() const { if( m_pCurSteps[p] == NULL ) { - LOG->Warn( "GetEasiestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); + LuaHelpers::ReportScriptErrorFmt( "GetEasiestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); continue; } dc = min( dc, m_pCurSteps[p]->GetDifficulty() ); @@ -2100,7 +2100,7 @@ Difficulty GameState::GetHardestStepsDifficulty() const { if( m_pCurSteps[p] == NULL ) { - LOG->Warn( "GetHardestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); + LuaHelpers::ReportScriptErrorFmt( "GetHardestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); continue; } dc = max( dc, m_pCurSteps[p]->GetDifficulty() ); diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index ff8b9c0923..840eaf4f2d 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -789,7 +789,7 @@ bool LuaHelpers::LoadScript( Lua *L, const RString &sScript, const RString &sNam return true; } -void LuaHelpers::ReportScriptError(RString const& Error) +void LuaHelpers::ReportScriptError(RString const& Error, RString ErrorType) { size_t line_break_pos= Error.find('\n'); RString short_error; @@ -803,6 +803,17 @@ void LuaHelpers::ReportScriptError(RString const& Error) } SCREENMAN->SystemMessage(short_error); LOG->Warn(Error.c_str()); + Dialog::OK(Error, ErrorType); +} + +// For convenience when replacing uses of LOG->Warn. +void LuaHelpers::ReportScriptErrorFmt(const char *fmt, ...) +{ + va_list va; + va_start( va, fmt ); + RString Buff = vssprintf( fmt, va ); + va_end( va ); + ReportScriptError(Buff); } bool LuaHelpers::RunScriptOnStack( Lua *L, RString &Error, int Args, int ReturnValues, bool ReportError ) @@ -862,11 +873,9 @@ bool LuaHelpers::RunScript( Lua *L, const RString &Script, const RString &Name, bool LuaHelpers::RunExpression( Lua *L, const RString &sExpression, const RString &sName ) { - RString sError; - if( !LuaHelpers::RunScript(L, "return " + sExpression, sName.empty()? RString("in"):sName, sError, 0, 1) ) + RString sError= ssprintf("Lua runtime error parsing \"%s\": ", sName.size()? sName.c_str():sExpression.c_str()); + if(!LuaHelpers::RunScript(L, "return " + sExpression, sName.empty()? RString("in"):sName, sError, 0, 1, true)) { - sError = ssprintf( "Lua runtime error parsing \"%s\": %s", sName.size()? sName.c_str():sExpression.c_str(), sError.c_str() ); - Dialog::OK( sError, "LUA_ERROR" ); return false; } return true; diff --git a/src/LuaManager.h b/src/LuaManager.h index 19ce04a377..8fc8d820aa 100644 --- a/src/LuaManager.h +++ b/src/LuaManager.h @@ -59,7 +59,9 @@ namespace LuaHelpers bool LoadScript( Lua *L, const RString &sScript, const RString &sName, RString &sError ); /* Report the error through the log and on the screen. */ - void ReportScriptError(RString const& Error); + void ReportScriptError(RString const& Error, RString ErrorType= "LUA_ERROR"); + // For convenience when replacing uses of LOG->Warn. + void ReportScriptErrorFmt(const char *fmt, ...); /* Run the function with arguments at the top of the stack, with the given * number of arguments. The specified number of return values are left on diff --git a/src/LyricsLoader.cpp b/src/LyricsLoader.cpp index 9909416cd8..d617df2144 100644 --- a/src/LyricsLoader.cpp +++ b/src/LyricsLoader.cpp @@ -24,7 +24,7 @@ bool LyricsLoader::LoadFromLRCFile(const RString& sPath, Song& out) RageFile input; if( !input.Open(sPath) ) { - LOG->Warn("Error opening file '%s' for reading: %s", sPath.c_str(), input.GetError().c_str() ); + LuaHelpers::ReportScriptErrorFmt("Error opening file '%s' for reading: %s", sPath.c_str(), input.GetError().c_str() ); return false; } @@ -40,7 +40,7 @@ bool LyricsLoader::LoadFromLRCFile(const RString& sPath, Song& out) break; if( ret == -1 ) { - LOG->Warn("Error reading %s: %s", input.GetPath().c_str(), input.GetError().c_str() ); + LuaHelpers::ReportScriptErrorFmt("Error reading %s: %s", input.GetPath().c_str(), input.GetError().c_str() ); break; } diff --git a/src/MusicWheel.cpp b/src/MusicWheel.cpp index 9eb5d728a1..f635cb45a5 100644 --- a/src/MusicWheel.cpp +++ b/src/MusicWheel.cpp @@ -1613,7 +1613,7 @@ Song *MusicWheel::GetPreferredSelectionForRandomOrPortal() return wid[iSelection]->m_pSong; } } - LOG->Warn( "Couldn't find any songs" ); + LuaHelpers::ReportScriptError( "Couldn't find any songs" ); return wid[0]->m_pSong; } diff --git a/src/NoteSkinManager.cpp b/src/NoteSkinManager.cpp index 9194574f50..fa4b5415bc 100644 --- a/src/NoteSkinManager.cpp +++ b/src/NoteSkinManager.cpp @@ -419,7 +419,7 @@ Actor *NoteSkinManager::LoadActor( const RString &sButton, const RString &sEleme // Make sure pActor is a Sprite (or something derived from Sprite). Sprite *pSprite = dynamic_cast( pRet ); if( pSprite == NULL ) - LOG->Warn( "%s: %s %s must be a Sprite", m_sCurrentNoteSkin.c_str(), sButton.c_str(), sElement.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "%s: %s %s must be a Sprite", m_sCurrentNoteSkin.c_str(), sButton.c_str(), sElement.c_str() ); } return pRet; diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index e24665cddc..03294998b0 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -824,7 +824,7 @@ public: { if(m_pLuaTable->GetLuaType() != LUA_TTABLE) { - LOG->Warn("LUA_ERROR: Result of \"%s\" is not a table.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: Result of \"%s\" is not a table.", RowName.c_str()); return false; } m_pLuaTable->PushSelf(L); @@ -832,7 +832,7 @@ public: const char *pStr = lua_tostring(L, -1); if( pStr == NULL ) { - LOG->Warn("LUA_ERROR: \"%s\" \"Name\" entry is not a string.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"Name\" entry is not a string.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -841,7 +841,7 @@ public: 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()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"LayoutType\" entry is not a string.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -850,7 +850,7 @@ public: 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()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"SelectType\" entry is not a string.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -858,12 +858,12 @@ public: lua_getfield(L, -1, "Choices"); if(!lua_istable(L, -1)) { - LOG->Warn("LUA_ERROR: \"%s\" \"Choices\" is not a table.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("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()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"Choices\" table contains a non-string.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -873,7 +873,7 @@ public: { if(!lua_isfunction(L, -1)) { - LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" is not a function.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"EnabledForPlayers\" is not a function.", RowName.c_str()); return false; } m_pLuaTable->PushSelf( L ); @@ -881,7 +881,7 @@ public: LuaHelpers::RunScriptOnStack(L, error, 1, 1, true); if(!lua_istable(L, -1)) { - LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" did not return a table.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"EnabledForPlayers\" did not return a table.", RowName.c_str()); return false; } lua_pushnil(L); @@ -890,7 +890,7 @@ public: PlayerNumber pn= Enum::Check(L, -1, true, true); if(pn == PlayerNumber_Invalid) { - LOG->Warn("LUA_ERROR: \"%s\" \"EnabledForPlayers\" contains a non-PlayerNumber.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"EnabledForPlayers\" contains a non-PlayerNumber.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -903,12 +903,12 @@ public: { if(!lua_istable(L, -1)) { - LOG->Warn("LUA_ERROR: \"%s\" \"ReloadRowMessages\" is not a table.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("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()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"ReloadRowMessages\" table contains a non-string.", RowName.c_str()); return false; } } @@ -917,7 +917,7 @@ public: lua_getfield(L, -1, "LoadSelections"); if(!lua_isfunction(L, -1)) { - LOG->Warn("LUA_ERROR: \"%s\" \"LoadSelections\" entry is not a function.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"LoadSelections\" entry is not a function.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -925,7 +925,7 @@ public: lua_getfield(L, -1, "SaveSelections"); if(!lua_isfunction(L, -1)) { - LOG->Warn("LUA_ERROR: \"%s\" \"SaveSelections\" entry is not a function.", RowName.c_str()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"SaveSelections\" entry is not a function.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -933,7 +933,7 @@ public: 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()); + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"NotifyOfSelection\" entry is not a function.", RowName.c_str()); return false; } lua_pop(L, 1); @@ -1278,7 +1278,7 @@ public: ConfOption *pConfOption = ConfOption::Find( sParam ); if( pConfOption == NULL ) { - LOG->Warn( "Invalid Conf type \"%s\"", sParam.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Invalid Conf type \"%s\"", sParam.c_str() ); pConfOption = ConfOption::Find( "Invalid" ); ASSERT_M( pConfOption != NULL, "ConfOption::Find(Invalid)" ); } diff --git a/src/PrefsManager.cpp b/src/PrefsManager.cpp index 2521b50054..cb17ace911 100644 --- a/src/PrefsManager.cpp +++ b/src/PrefsManager.cpp @@ -535,7 +535,7 @@ public: IPreference *pPref = IPreference::GetPreferenceByName( sName ); if( pPref == NULL ) { - LOG->Warn( "GetPreference: unknown preference \"%s\"", sName.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "GetPreference: unknown preference \"%s\"", sName.c_str() ); lua_pushnil( L ); return 1; } @@ -550,7 +550,7 @@ public: IPreference *pPref = IPreference::GetPreferenceByName( sName ); if( pPref == NULL ) { - LOG->Warn( "SetPreference: unknown preference \"%s\"", sName.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "SetPreference: unknown preference \"%s\"", sName.c_str() ); return 0; } @@ -565,7 +565,7 @@ public: IPreference *pPref = IPreference::GetPreferenceByName( sName ); if( pPref == NULL ) { - LOG->Warn( "SetPreferenceToDefault: unknown preference \"%s\"", sName.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "SetPreferenceToDefault: unknown preference \"%s\"", sName.c_str() ); return 0; } diff --git a/src/Profile.cpp b/src/Profile.cpp index c8b17cd1af..764bd59f8e 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -905,7 +905,7 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature int iBytes = pFile->GetFileSize(); if( iBytes > MAX_PLAYER_STATS_XML_SIZE_BYTES ) { - LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); return ProfileLoadResult_FailedTampered; } } @@ -919,7 +919,7 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature // verify the stats.xml signature with the "don't share" file if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) ) { - LOG->Warn( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); return ProfileLoadResult_FailedTampered; } LOG->Trace( "Done." ); @@ -928,7 +928,7 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature LOG->Trace( "Verifying stats.xml signature" ); if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) ) { - LOG->Warn( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); return ProfileLoadResult_FailedTampered; } LOG->Trace( "Done." ); @@ -1065,7 +1065,7 @@ bool Profile::SaveStatsXmlToDir( RString sDir, bool bSignData ) const RageFile f; if( !f.Open(fn, RageFile::WRITE) ) { - LOG->Warn( "Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str() ); return false; } @@ -1288,7 +1288,7 @@ ProfileLoadResult Profile::LoadEditableDataFromDir( RString sDir ) int iBytes = FILEMAN->GetFileSizeInBytes( fn ); if( iBytes > MAX_EDITABLE_INI_SIZE_BYTES ) { - LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); return ProfileLoadResult_FailedTampered; } diff --git a/src/ScreenOptions.cpp b/src/ScreenOptions.cpp index a368adb77e..ecb23a5d5a 100644 --- a/src/ScreenOptions.cpp +++ b/src/ScreenOptions.cpp @@ -554,7 +554,7 @@ void ScreenOptions::HandleScreenMessage( const ScreenMessage SM ) // If options set a NextScreen or one is specified in metrics, then fade out if( GetNextScreenName() == "" ) { - LOG->Warn( "%s::HandleScreenMessage: Tried to fade out, but we have no next screen", m_sName.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "%s::HandleScreenMessage: Tried to fade out, but we have no next screen", m_sName.c_str() ); return; } diff --git a/src/ScreenOptionsMaster.cpp b/src/ScreenOptionsMaster.cpp index 9ce88793fb..662885dbd5 100644 --- a/src/ScreenOptionsMaster.cpp +++ b/src/ScreenOptionsMaster.cpp @@ -58,12 +58,14 @@ void ScreenOptionsMaster::Init() OptionRowHandler *pHand = OptionRowHandlerUtil::Make( cmds ); if( pHand == NULL ) - RageException::Throw( "Invalid OptionRowHandler \"%s\" in \"%s::Line%i\".", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), i ); - OptionRowHandlers.push_back( pHand ); + { + LuaHelpers::ReportScriptErrorFmt("Invalid OptionRowHandler \"%s\" in \"%s::Line%i\".", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), i); + } + else + { + OptionRowHandlers.push_back( pHand ); + } } - - ASSERT( OptionRowHandlers.size() == asLineNames.size() ); - InitMenu( OptionRowHandlers ); } diff --git a/src/ScreenSMOnlineLogin.cpp b/src/ScreenSMOnlineLogin.cpp index 2a59449954..248b9f4066 100644 --- a/src/ScreenSMOnlineLogin.cpp +++ b/src/ScreenSMOnlineLogin.cpp @@ -118,7 +118,7 @@ void ScreenSMOnlineLogin::HandleScreenMessage(const ScreenMessage SM) LOG->Trace("[ScreenSMOnlineLogin::HandleScreenMessage] SMOnlinePack"); if(!GAMESTATE->IsPlayerEnabled((PlayerNumber) m_iPlayer)) { - LOG->Warn("Invalid player number: %i", m_iPlayer); + LuaHelpers::ReportScriptErrorFmt("Invalid player number: %i", m_iPlayer); return; } diff --git a/src/ScreenSelectMaster.cpp b/src/ScreenSelectMaster.cpp index 24118fa488..0d3b8fd6c6 100644 --- a/src/ScreenSelectMaster.cpp +++ b/src/ScreenSelectMaster.cpp @@ -195,7 +195,7 @@ void ScreenSelectMaster::Init() int from, to; if( sscanf( parts[part], "%d:%d", &from, &to ) != 2 ) { - LOG->Warn( "%s::OptionOrder%s parse error", m_sName.c_str(), MenuDirToString(dir).c_str() ); + LuaHelpers::ReportScriptErrorFmt( "%s::OptionOrder%s parse error", m_sName.c_str(), MenuDirToString(dir).c_str() ); continue; } diff --git a/src/ScreenWithMenuElements.cpp b/src/ScreenWithMenuElements.cpp index e9304ac070..c54f6adef5 100644 --- a/src/ScreenWithMenuElements.cpp +++ b/src/ScreenWithMenuElements.cpp @@ -9,7 +9,6 @@ #include "GameSoundManager.h" #include "MemoryCardDisplay.h" #include "InputEventPlus.h" -#include "arch/Dialog/Dialog.h" // wow I import this for JUST ONE THING. -aj #define TIMER_STEALTH THEME->GetMetricB(m_sName,"TimerStealth") #define SHOW_STAGE_DISPLAY THEME->GetMetricB(m_sName,"ShowStageDisplay") @@ -203,7 +202,6 @@ void ScreenWithMenuElements::StartPlayingMusic() if( !LuaHelpers::RunScript(L, Script, "@"+m_sPathToMusic, Error, 0, 1, true) ) { LUA->Release( L ); - Dialog::OK( Error, "LUA_ERROR" ); return; } else diff --git a/src/SongManager.cpp b/src/SongManager.cpp index 6a0eebfe9a..7a9840ad28 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -935,7 +935,7 @@ void SongManager::InitRandomAttacks() MsdFile msd; if( !msd.ReadFile( ATTACK_FILE, true ) ) - LOG->Warn( "Error opening file '%s' for reading: %s.", ATTACK_FILE.c_str(), msd.GetError().c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Error opening file '%s' for reading: %s.", ATTACK_FILE.c_str(), msd.GetError().c_str() ); else { for( unsigned i=0; i 2 ) { - LOG->Warn( "Got \"%s:%s\" tag with too many parameters", sType.c_str(), sAttack.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Got \"%s:%s\" tag with too many parameters", sType.c_str(), sAttack.c_str() ); continue; } if( !sType.EqualsNoCase("ATTACK") ) { - LOG->Warn( "Got \"%s:%s\" tag with wrong declaration", sType.c_str(), sAttack.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Got \"%s:%s\" tag with wrong declaration", sType.c_str(), sAttack.c_str() ); continue; } @@ -1219,7 +1219,7 @@ void SongManager::GetExtraStageInfo( bool bExtra2, const Style *sd, Song*& pSong if( GAMESTATE->m_pCurSong == NULL ) { // This normally shouldn't happen, but it's helpful to permit it for testing. - LOG->Warn( "GetExtraStageInfo() called in GROUP_ALL, but GAMESTATE->m_pCurSong == NULL" ); + LuaHelpers::ReportScriptErrorFmt( "GetExtraStageInfo() called in GROUP_ALL, but GAMESTATE->m_pCurSong == NULL" ); GAMESTATE->m_pCurSong.Set( GetRandomSong() ); } sGroup = GAMESTATE->m_pCurSong->m_sGroupName; @@ -1737,7 +1737,7 @@ void SongManager::LoadStepEditsFromProfileDir( const RString &sProfileDir, Profi if( (int) vsFiles.size() > MAX_EDIT_STEPS_PER_PROFILE - iNumEditsLoaded ) { - LOG->Warn("Profile %s has too many edits; some have been skipped.", ProfileSlotToString( slot ).c_str() ); + LuaHelpers::ReportScriptErrorFmt("Profile %s has too many edits; some have been skipped.", ProfileSlotToString( slot ).c_str() ); return; } @@ -1779,7 +1779,7 @@ void SongManager::LoadStepEditsFromProfileDir( const RString &sProfileDir, Profi if( (int) vsEdits.size() > MAX_EDIT_STEPS_PER_PROFILE - iNumEditsLoaded ) { - LOG->Warn("Profile %s has too many edits; some have been skipped.", ProfileSlotToString( slot ).c_str() ); + LuaHelpers::ReportScriptErrorFmt("Profile %s has too many edits; some have been skipped.", ProfileSlotToString( slot ).c_str() ); return; } diff --git a/src/Sprite.cpp b/src/Sprite.cpp index c3af0d668e..a2e6959f8b 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -10,7 +10,6 @@ #include "RageTexture.h" #include "RageUtil.h" #include "ActorUtil.h" -#include "arch/Dialog/Dialog.h" #include "Foreach.h" #include "LuaBinding.h" #include "LuaManager.h" @@ -743,7 +742,7 @@ void Sprite::SetState( int iNewState ) else sError = ssprintf("A Sprite (\"%s\") tried to set state index %d, but no texture is loaded.", this->m_sName.c_str(), iNewState ); - Dialog::OK( sError, "SPRITE_INVALID_FRAME" ); + LuaHelpers::ReportScriptError(sError, "SPRITE_INVALID_FRAME"); } } diff --git a/src/Style.cpp b/src/Style.cpp index b550311a74..13273c8102 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -132,7 +132,7 @@ public: int iCol = IArg(2) - 1; if( iCol < 0 || iCol >= p->m_iColsPerPlayer ) { - LOG->Warn( "Style:GetColumnDrawOrder(): column %i out of range( 1 to %i )", iCol+1, p->m_iColsPerPlayer ); + LuaHelpers::ReportScriptErrorFmt( "Style:GetColumnDrawOrder(): column %i out of range( 1 to %i )", iCol+1, p->m_iColsPerPlayer ); return 0; } @@ -153,7 +153,7 @@ public: int iCol = IArg(1) - 1; if( iCol < 0 || iCol >= p->m_iColsPerPlayer*NUM_PLAYERS ) { - LOG->Warn( "Style:GetColumnDrawOrder(): column %i out of range( 1 to %i )", iCol+1, p->m_iColsPerPlayer*NUM_PLAYERS ); + LuaHelpers::ReportScriptErrorFmt( "Style:GetColumnDrawOrder(): column %i out of range( 1 to %i )", iCol+1, p->m_iColsPerPlayer*NUM_PLAYERS ); return 0; } lua_pushnumber( L, p->m_iColumnDrawOrder[iCol]+1 ); diff --git a/src/TitleSubstitution.cpp b/src/TitleSubstitution.cpp index 5f3f698770..b06380fbd6 100644 --- a/src/TitleSubstitution.cpp +++ b/src/TitleSubstitution.cpp @@ -6,6 +6,7 @@ #include "FontCharAliases.h" #include "RageFile.h" #include "Foreach.h" +#include "LuaManager.h" #include "XmlFile.h" #include "XmlFileUtil.h" @@ -62,7 +63,7 @@ void TitleTrans::LoadFromNode( const XNode* pNode ) else if( sKeyName == "ArtistTransTo") Replacement.ArtistTranslit = sValue; else if( sKeyName == "SubtitleTransTo") Replacement.SubtitleTranslit = sValue; else - LOG->Warn( "Unknown TitleSubst tag: \"%s\"", sKeyName.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unknown TitleSubst tag: \"%s\"", sKeyName.c_str() ); } } diff --git a/src/UnlockManager.cpp b/src/UnlockManager.cpp index ae77104499..3d88380775 100644 --- a/src/UnlockManager.cpp +++ b/src/UnlockManager.cpp @@ -106,7 +106,7 @@ RString UnlockManager::FindEntryID( const RString &sName ) const if( pEntry == NULL ) { - LOG->Warn( "Couldn't find locked entry \"%s\"", sName.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Couldn't find locked entry \"%s\"", sName.c_str() ); return ""; } @@ -578,20 +578,20 @@ void UnlockManager::Load() case UnlockRewardType_Song: e->m_Song.FromSong( SONGMAN->FindSong( e->m_cmd.GetArg(0).s ) ); if( !e->m_Song.IsValid() ) - LOG->Warn( "Unlock: Cannot find song matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unlock: Cannot find song matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); break; case UnlockRewardType_Steps: e->m_Song.FromSong( SONGMAN->FindSong( e->m_cmd.GetArg(0).s ) ); if( !e->m_Song.IsValid() ) { - LOG->Warn( "Unlock: Cannot find song matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unlock: Cannot find song matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); break; } e->m_dc = StringToDifficulty( e->m_cmd.GetArg(1).s ); if( e->m_dc == Difficulty_Invalid ) { - LOG->Warn( "Unlock: Invalid difficulty \"%s\"", e->m_cmd.GetArg(1).s.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unlock: Invalid difficulty \"%s\"", e->m_cmd.GetArg(1).s.c_str() ); break; } @@ -601,21 +601,21 @@ void UnlockManager::Load() e->m_Song.FromSong( SONGMAN->FindSong( e->m_cmd.GetArg(0).s ) ); if( !e->m_Song.IsValid() ) { - LOG->Warn( "Unlock: Cannot find song matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unlock: Cannot find song matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); break; } e->m_dc = StringToDifficulty( e->m_cmd.GetArg(1).s ); if( e->m_dc == Difficulty_Invalid ) { - LOG->Warn( "Unlock: Invalid difficulty \"%s\"", e->m_cmd.GetArg(1).s.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unlock: Invalid difficulty \"%s\"", e->m_cmd.GetArg(1).s.c_str() ); break; } e->m_StepsType = GAMEMAN->StringToStepsType(e->m_cmd.GetArg(2).s); if (e->m_StepsType == StepsType_Invalid) { - LOG->Warn( "Unlock: Invalid steps type \"%s\"", e->m_cmd.GetArg(2).s.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unlock: Invalid steps type \"%s\"", e->m_cmd.GetArg(2).s.c_str() ); break; } break; @@ -623,7 +623,7 @@ void UnlockManager::Load() case UnlockRewardType_Course: e->m_Course.FromCourse( SONGMAN->FindCourse(e->m_cmd.GetArg(0).s) ); if( !e->m_Course.IsValid() ) - LOG->Warn( "Unlock: Cannot find course matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unlock: Cannot find course matching \"%s\"", e->m_cmd.GetArg(0).s.c_str() ); break; case UnlockRewardType_Modifier: // nothing to cache diff --git a/src/XmlFileUtil.cpp b/src/XmlFileUtil.cpp index 81b787d122..f7d5f1664e 100644 --- a/src/XmlFileUtil.cpp +++ b/src/XmlFileUtil.cpp @@ -7,6 +7,7 @@ #include "RageLog.h" #include "arch/Dialog/Dialog.h" #include "Foreach.h" +#include "LuaManager.h" bool XmlFileUtil::LoadFromFileShowErrors( XNode &xml, RageFileBasic &f ) { @@ -20,8 +21,7 @@ bool XmlFileUtil::LoadFromFileShowErrors( XNode &xml, RageFileBasic &f ) return true; RString sWarning = ssprintf( "XML: LoadFromFile failed: %s", sError.c_str() ); - LOG->Warn( "%s", sWarning.c_str() ); - Dialog::OK( sWarning, "XML_PARSE_ERROR" ); + LuaHelpers::ReportScriptError(sWarning, "XML_PARSE_ERROR"); return false; } @@ -30,7 +30,7 @@ bool XmlFileUtil::LoadFromFileShowErrors( XNode &xml, const RString &sFile ) RageFile f; if( !f.Open(sFile, RageFile::READ) ) { - LOG->Warn("Couldn't open %s for reading: %s", sFile.c_str(), f.GetError().c_str() ); + LuaHelpers::ReportScriptErrorFmt("Couldn't open %s for reading: %s", sFile.c_str(), f.GetError().c_str() ); return false; } @@ -38,8 +38,7 @@ bool XmlFileUtil::LoadFromFileShowErrors( XNode &xml, const RString &sFile ) if( !bSuccess ) { RString sWarning = ssprintf( "XML: LoadFromFile failed for file: %s", sFile.c_str() ); - LOG->Warn( "%s", sWarning.c_str() ); - Dialog::OK( sWarning, "XML_PARSE_ERROR" ); + LuaHelpers::ReportScriptError(sWarning, "XML_PARSE_ERROR"); } return bSuccess; } @@ -518,14 +517,13 @@ bool XmlFileUtil::SaveToFile( const XNode *pNode, const RString &sFile, const RS RageFile f; if( !f.Open(sFile, RageFile::WRITE) ) { - LOG->Warn( "Couldn't open %s for writing: %s", sFile.c_str(), f.GetError().c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Couldn't open %s for writing: %s", sFile.c_str(), f.GetError().c_str() ); return false; } return SaveToFile( pNode, f, sStylesheet, bWriteTabs ); } -#include "LuaManager.h" #include "LuaReference.h" class XNodeLuaValue: public XNodeValue { From bee284d0c764d5ea4aed320958e4fc6d10a7d5e5 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 8 Jul 2014 01:02:36 -0600 Subject: [PATCH 04/12] Rewrote ScreenSystemOverlay overlay/default.lua to be able to display multiple messages. Added Reload Overlay Screens option to debug menu. --- .../ScreenSystemLayer errorbg.lua | 14 ++ .../ScreenSystemLayer overlay/default.lua | 166 +++++++++++++++--- Themes/_fallback/Languages/en.ini | 1 + src/ScreenDebugOverlay.cpp | 15 ++ src/ScreenManager.cpp | 13 ++ src/ScreenManager.h | 5 + 6 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua new file mode 100644 index 0000000000..94615c7876 --- /dev/null +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua @@ -0,0 +1,14 @@ +return Def.ActorFrame{ + Def.Quad { + Name= "erp", + InitCommand= function(self) + self:setsize(SCREEN_WIDTH, SCREEN_HEIGHT) + self:horizalign(left) + self:vertalign(top) + self:diffuse(color("0,0,0,0")) + self:diffusealpha(.85) + end, + SetCoveredHeightCommand= function(self, param) + end + } +} diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua index 0e638d6afe..f2c09ff8a4 100644 --- a/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua @@ -64,27 +64,147 @@ t[#t+1] = Def.ActorFrame { CreditsText( PLAYER_2 ); }; -- Text -t[#t+1] = Def.ActorFrame { - Def.Quad { - InitCommand=cmd(zoomtowidth,SCREEN_WIDTH;zoomtoheight,30;horizalign,left;vertalign,top;y,SCREEN_TOP;diffuse,color("0,0,0,0")); - OnCommand=cmd(finishtweening;diffusealpha,0.85;); - OffCommand=cmd(sleep,3;linear,0.5;diffusealpha,0;); - }; - LoadFont("Common","Normal") .. { - Name="Text"; - InitCommand=cmd(maxwidth,750;horizalign,left;vertalign,top;y,SCREEN_TOP+10;x,SCREEN_LEFT+10;shadowlength,1;diffusealpha,0;); - OnCommand=cmd(finishtweening;diffusealpha,1;zoom,0.5); - OffCommand=cmd(sleep,3;linear,0.5;diffusealpha,0;); - }; - SystemMessageMessageCommand = function(self, params) - self:GetChild("Text"):settext( params.Message ); - self:playcommand( "On" ); - if params.NoAnimate then - self:finishtweening(); - end - self:playcommand( "Off" ); - end; - HideSystemMessageMessageCommand = cmd(finishtweening); -}; -return t; +-- Minor text formatting functions from Kyzentun. +-- TODO: Figure out why BitmapText:maxwidth doesn't do what I want. +local function width_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or 1 + if text:GetWidth() * natural_zoom > limit then + text:zoomx(limit / text:GetWidth()) + else + text:zoomx(natural_zoom) + end +end + +local function width_clip_text(text, limit) + local full_text= text:GetText() + local fits= text:GetZoomedWidth() <= limit + local prev_max= #full_text - 1 + local prev_min= 0 + if not fits then + while prev_max - prev_min > 1 do + local new_max= math.round((prev_max + prev_min) / 2) + text:settext(full_text:sub(1, 1+new_max)) + if text:GetZoomedWidth() <= limit then + prev_min= new_max + else + prev_max= new_max + end + end + text:settext(full_text:sub(1, 1+prev_min)) + end +end + +local function width_clip_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or text:GetZoomY() + local text_width= text:GetWidth() * natural_zoom + if text_width > limit * 2 then + text:zoomx(natural_zoom * .5) + width_clip_text(text, limit) + else + width_limit_text(text, limit, natural_zoom) + end +end + +local errorbg +local err_frame +local text_actors= {} +local line_height= 12 -- A good line height for Common Normal at .5 zoom. +local line_width= SCREEN_WIDTH - 20 + +local next_message_actor= 1 + +local min_message_time= { show= 4, hide= .125} +local message_time= { + show= min_message_time.show, hide= min_message_time.hide} + +function SetOverlayMessageTime(t, which) + if not min_message_time[which] then + SCREENMAN:SystemMessage("Attempted to set invalid overlay message time field: " .. tostring(which)) + return + end + if t < min_message_time[which] then + SCREENMAN:SystemMessage( + "Attempted to set overlay message " .. which .. + " time to below minimum of " .. min_message_time[which] .. ".") + return + end + message_time[which]= t +end + +local errbg_actor= LoadActor(THEME:GetPathB("ScreenSystemLayer", "errorbg")) +-- Force the name of the returned actor so that we can easily use playcommand on it later. +errbg_actor.Name= "errorbg" + +local frame_args= { + Name="Error frame", + InitCommand= function(self) + err_frame= self + errorbg= self:GetChild("errorbg") + self:y(-SCREEN_HEIGHT) + end, + SystemMessageMessageCommand = function(self, params) + err_frame:stoptweening() + err_frame:visible(true) + local covered_height= line_height * (next_message_actor-1) + err_frame:y(-SCREEN_HEIGHT + covered_height) + if errorbg then + errorbg:playcommand("SetCoveredHeight", {height= covered_height}) + end + err_frame:sleep(message_time.show) + err_frame:queuecommand("DecNextActor") + -- Shift the text on all the actors being shown up by one. + for i= next_message_actor, 1, -1 do + if text_actors[i] then + text_actors[i]:visible(true) + if i > 1 and text_actors[i-1] then + text_actors[i]:settext(text_actors[i-1]:GetText()) + else + text_actors[i]:settext(params.Message) + end + width_clip_limit_text(text_actors[i], line_width) + end + end + if next_message_actor <= #text_actors and not params.NoAnimate then + next_message_actor= next_message_actor + 1 + end + end, + DecNextActorCommand= function(self) + self:linear(message_time.hide) + self:y(self:GetY()-line_height) + if text_actors[next_message_actor] then + text_actors[next_message_actor]:visible(false) + end + next_message_actor= next_message_actor - 1 + if next_message_actor > 1 then + self:queuecommand("DecNextActor") + else + self:queuecommand("Off") + end + end, + OffCommand= cmd(visible,false), + HideSystemMessageMessageCommand = cmd(finishtweening), + errbg_actor, +} +-- Create enough text actors that we can fill the screen. +local num_text= SCREEN_HEIGHT / line_height +for i= 1, num_text do + frame_args[#frame_args+1]= LoadFont("Common","Normal") .. { + Name="Text" .. i, + InitCommand= function(self) + -- Put them in the list in reverse order so the ones at the bottom of the screen are used first. + text_actors[num_text-i+1]= self + self:horizalign(left) + self:vertalign(top) + self:x(SCREEN_LEFT + 10) + self:y(SCREEN_TOP + (line_height * (i-1)) + 2) + self:shadowlength(1) + self:zoom(.5) + self:visible(false) + end, + OffCommand= cmd(visible,false), + } +end +t[#t+1] = Def.ActorFrame(frame_args) + +return t diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 237922b3cd..448a35b2e7 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -1317,6 +1317,7 @@ Profile=Profile Pull Back Camera=Pull Back Camera Restart=Restart Reload=Reload +Reload Overlay Screens=Reload Overlay Screens Reload Theme and Textures=Reload Theme and Textures Rendering Stats=Rendering Stats Screen Test Mode=Screen Test Mode diff --git a/src/ScreenDebugOverlay.cpp b/src/ScreenDebugOverlay.cpp index 3cdfe08a9f..e931f38e42 100644 --- a/src/ScreenDebugOverlay.cpp +++ b/src/ScreenDebugOverlay.cpp @@ -548,6 +548,7 @@ static LocalizedString RELOAD ( "ScreenDebugOverlay", "Reload" ); static LocalizedString RESTART ( "ScreenDebugOverlay", "Restart" ); static LocalizedString SCREEN_ON ( "ScreenDebugOverlay", "Send On To Screen" ); static LocalizedString SCREEN_OFF ( "ScreenDebugOverlay", "Send Off To Screen" ); +static LocalizedString RELOAD_OVERLAY_SCREENS( "ScreenDebugOverlay", "Reload Overlay Screens" ); static LocalizedString RELOAD_THEME_AND_TEXTURES( "ScreenDebugOverlay", "Reload Theme and Textures" ); static LocalizedString WRITE_PROFILES ( "ScreenDebugOverlay", "Write Profiles" ); static LocalizedString WRITE_PREFERENCES ( "ScreenDebugOverlay", "Write Preferences" ); @@ -1030,6 +1031,19 @@ class DebugLineReloadTheme : public IDebugLine } }; +class DebugLineReloadOverlayScreens : public IDebugLine +{ + virtual RString GetDisplayTitle() { return RELOAD_OVERLAY_SCREENS.GetValue(); } + virtual RString GetDisplayValue() { return RString(); } + virtual bool IsEnabled() { return true; } + virtual RString GetPageName() const { return "Theme"; } + virtual void DoAndLog( RString &sMessageOut ) + { + IDebugLine::DoAndLog(sMessageOut); + SCREENMAN->ReloadOverlayScreensAfterInputFinishes(); + } +}; + class DebugLineWriteProfiles : public IDebugLine { virtual RString GetDisplayTitle() { return WRITE_PROFILES.GetValue(); } @@ -1212,6 +1226,7 @@ DECLARE_ONE( DebugLineRestartCurrentScreen ); DECLARE_ONE( DebugLineCurrentScreenOn ); DECLARE_ONE( DebugLineCurrentScreenOff ); DECLARE_ONE( DebugLineReloadTheme ); +DECLARE_ONE( DebugLineReloadOverlayScreens ); DECLARE_ONE( DebugLineWriteProfiles ); DECLARE_ONE( DebugLineWritePreferences ); DECLARE_ONE( DebugLineMenuTimer ); diff --git a/src/ScreenManager.cpp b/src/ScreenManager.cpp index bd24d9d2cc..e9079c43e1 100644 --- a/src/ScreenManager.cpp +++ b/src/ScreenManager.cpp @@ -242,6 +242,7 @@ ScreenManager::ScreenManager() g_pSharedBGA = new Actor; + m_bReloadOverlayScreensAfterInput= false; m_bZeroNextUpdate = false; m_PopTopScreen = SM_Invalid; m_OnDonePreparingScreen = SM_Invalid; @@ -330,6 +331,11 @@ void ScreenManager::ReloadOverlayScreens() this->RefreshCreditsMessages(); } +void ScreenManager::ReloadOverlayScreensAfterInputFinishes() +{ + m_bReloadOverlayScreensAfterInput= true; +} + Screen *ScreenManager::GetTopScreen() { if( g_ScreenStack.empty() ) @@ -514,7 +520,14 @@ void ScreenManager::Input( const InputEventPlus &input ) // because anybody setting an input callback is probably doing it to // do something in addition to whatever the screen does. if(pScreen->PassInputToLua(input) || handled) + { + if(m_bReloadOverlayScreensAfterInput) + { + ReloadOverlayScreens(); + m_bReloadOverlayScreensAfterInput= false; + } return; + } } // Pass input to the topmost screen. If we have a new top screen pending, don't diff --git a/src/ScreenManager.h b/src/ScreenManager.h index d9b98649e6..304cb77d5b 100644 --- a/src/ScreenManager.h +++ b/src/ScreenManager.h @@ -51,6 +51,7 @@ public: void RefreshCreditsMessages(); void ThemeChanged(); void ReloadOverlayScreens(); + void ReloadOverlayScreensAfterInputFinishes(); /** * @brief Is this Screen in the main Screen stack, but not the bottommost Screen? @@ -79,6 +80,10 @@ private: // operations take a long time, and will cause a skip on the next update. bool m_bZeroNextUpdate; + // This exists so the debug overlay can reload the overlay screens without seg faulting. + // It's "AfterInput" because the debug overlay carries out actions in Input. + bool m_bReloadOverlayScreensAfterInput; + Screen *MakeNewScreen( const RString &sName ); void LoadDelayedScreen(); bool ActivatePreparedScreenAndBackground( const RString &sScreenName ); From 63311ab62f98f24d3ab637735b0973bcc6ded4f4 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 8 Jul 2014 20:01:33 -0600 Subject: [PATCH 05/12] Moved error reporting to ScreenSystemLayer error.lua. ScreenSystemLayer overlay/default.lua restored to previous. Error messages are now broadcast with the ScriptError message instead of SystemMessage. Added show/hide error messages toggle and clear error messages to debug menu. Nuked ScreenConsoleOverlay for being unfinished and duplicating ScreenSystemLayer. Changed Actor:tween and Tween::CreateFromStack to use ReportScriptError on invalid tween params. Changed some OptionRowList things to use ReportScriptError on invalid metrics. --- .../BGAnimations/ScreenSystemLayer error.lua | 218 ++++++++++++++++++ .../ScreenSystemLayer errorbg.lua | 14 -- .../ScreenSystemLayer overlay/default.lua | 162 ++----------- Themes/_fallback/Languages/en.ini | 2 + Themes/_fallback/metrics.ini | 12 +- src/Actor.cpp | 5 +- src/DynamicActorScroller.cpp | 10 +- src/LuaManager.cpp | 6 +- src/OptionRowHandler.cpp | 27 ++- src/ScreenDebugOverlay.cpp | 34 ++- src/ScreenSystemLayer.cpp | 4 +- src/ScreenSystemLayer.h | 1 + src/Tween.cpp | 5 +- 13 files changed, 321 insertions(+), 179 deletions(-) create mode 100644 Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua delete mode 100644 Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua new file mode 100644 index 0000000000..4a675d2875 --- /dev/null +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua @@ -0,0 +1,218 @@ +-- If you are a common themer, DO NOT INCLUDE THIS FILE IN YOUR THEME. +-- This layer is purely for error reporting, so that you can see errors on screen easily while running stepmania. +-- If you include this file in your theme, you will not benefit from any improvements in error reporting. +-- If you want a different background behind the errors, then include "ScreenSystemLayer errorbg.lua" in your theme. +-- If you want to adjust how long errors stay on screen for, call the "SetErrorMessageTime" function. (see usage notes in comments above that function) + +-- Minor text formatting functions from Kyzentun. +-- TODO: Figure out why BitmapText:maxwidth doesn't do what I want. +local function width_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or 1 + if text:GetWidth() * natural_zoom > limit then + text:zoomx(limit / text:GetWidth()) + else + text:zoomx(natural_zoom) + end +end + +local function width_clip_text(text, limit) + local full_text= text:GetText() + local fits= text:GetZoomedWidth() <= limit + local prev_max= #full_text - 1 + local prev_min= 0 + if not fits then + while prev_max - prev_min > 1 do + local new_max= math.round((prev_max + prev_min) / 2) + text:settext(full_text:sub(1, 1+new_max)) + if text:GetZoomedWidth() <= limit then + prev_min= new_max + else + prev_max= new_max + end + end + text:settext(full_text:sub(1, 1+prev_min)) + end +end + +local function width_clip_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or text:GetZoomY() + local text_width= text:GetWidth() * natural_zoom + if text_width > limit * 2 then + text:zoomx(natural_zoom * .5) + width_clip_text(text, limit) + else + width_limit_text(text, limit, natural_zoom) + end +end + +local text_actors= {} +local line_height= 12 -- A good line height for Common Normal at .5 zoom. +local line_width= SCREEN_WIDTH - 20 + +local next_message_actor= 1 +local show_requested= false + +local min_message_time= {show= 1, hide= .03125} +local default_message_time= {show= 4, hide= .125} +local message_time= {} +for k, v in pairs(default_message_time) do + message_time[k]= v +end + +-- Example usage: +-- "SetErrorMessageTime('show' 5)" sets errors to show for 5 seconds before beginning to hide. +-- "SetErrorMessageTime('hide' .5)" sets errors to hide one error every .5 seconds after the show time has passed. +function SetErrorMessageTime(which, t) + if not min_message_time[which] then + MESSAGEMAN:Broadcast( + "ScriptError", { + Message= "Attempted to set invalid overlay message time field: " .. + tostring(which)}) + return + end + if t < min_message_time[which] then + MESSAGEMAN:Broadcast( + "ScriptError", { + Message= "Attempted to set overlay message " .. which .. + " time to below minimum of " .. min_message_time[which] .. "."}) + return + end + Trace("Setting error message " .. which .. " time to " .. t) + message_time[which]= t +end + +function GetErrorMessageTime(which) + return message_time[which] +end + +function GetErrorMessageTimeMin(which) + return min_message_time[which] +end + +function GetErrorMessageTimeDefault(which) + return default_message_time[which] +end + +local frame_args= { + Name="Error frame", + InitCommand= function(self) + self:y(-SCREEN_HEIGHT) + end, + ScriptErrorMessageCommand = function(self, params) + show_requested= false + self:stoptweening() + self:visible(true) + local covered_height= math.min(line_height * next_message_actor, SCREEN_HEIGHT) + self:y(-SCREEN_HEIGHT + covered_height) + self:sleep(message_time.show) + self:queuecommand("DecNextActor") + -- Shift the text on all the actors being shown up by one. + for i= next_message_actor, 1, -1 do + if text_actors[i] then + text_actors[i]:visible(true) + if i > 1 and text_actors[i-1] then + text_actors[i]:settext(text_actors[i-1]:GetText()) + else + text_actors[i]:settext(params.Message) + end + width_clip_limit_text(text_actors[i], line_width) + end + end + if next_message_actor <= #text_actors then + next_message_actor= next_message_actor + 1 + end + end, + ToggleErrorsMessageCommand= function(self, params) + if show_requested then + self:playcommand("HideErrors") + else + self:playcommand("ShowErrors") + end + end, + ShowErrorsMessageCommand= function(self, params) + show_requested= true + for i, mactor in ipairs(text_actors) do + if mactor:GetText() ~= "" then + mactor:visible(true) + next_message_actor= next_message_actor + 1 + end + end + next_message_actor= next_message_actor - 1 + local covered_height= math.min(line_height * next_message_actor, SCREEN_HEIGHT) + self:stoptweening() + self:visible(true) + self:linear(message_time.hide) + self:y(-SCREEN_HEIGHT + covered_height) + end, + HideErrorsMessageCommand= function(self, params) + if not show_requested then return end + show_requested= false + self:stoptweening() + next_message_actor= 1 + self:linear(message_time.hide) + self:y(-SCREEN_HEIGHT) + self:queuecommand("HideText") + end, + ClearErrorsMessageCommand= function(self, params) + -- This is so that someone using ShowErrorsMessageCommand can clear ones + -- they've dealt with. + -- Only allow clearing errors that we have scrolled off screen. + for i, mactor in ipairs(text_actors) do + if not mactor:GetVisible() then + mactor:settext("") + end + end + end, + DecNextActorCommand= function(self) + self:linear(message_time.hide) + self:y(self:GetY()-line_height) + if text_actors[next_message_actor] then + text_actors[next_message_actor]:visible(false) + end + next_message_actor= next_message_actor - 1 + if next_message_actor > 1 then + self:queuecommand("DecNextActor") + else + self:queuecommand("Off") + end + end, + HideTextCommand= function(self) + for i, mactor in ipairs(text_actors) do + mactor:visible(false) + end + end, + OffCommand= cmd(visible,false), + Def.Quad { + Name= "errorbg", + InitCommand= function(self) + self:setsize(SCREEN_WIDTH, SCREEN_HEIGHT) + self:horizalign(left) + self:vertalign(top) + self:diffuse(color("0,0,0,0")) + self:diffusealpha(.85) + end, + } +} +-- Create enough text actors that we can fill the screen. +local num_text= SCREEN_HEIGHT / line_height +for i= 1, num_text do + frame_args[#frame_args+1]= LoadFont("Common","Normal") .. { + Name="Text" .. i, + InitCommand= function(self) + -- Put them in the list in reverse order so the ones at the bottom of the screen are used first. + text_actors[num_text-i+1]= self + self:horizalign(left) + self:vertalign(top) + self:x(SCREEN_LEFT + 10) + self:y(SCREEN_TOP + (line_height * (i-1)) + 2) + self:shadowlength(1) + self:zoom(.5) + self:visible(false) + end, + OffCommand= cmd(visible,false), + } +end + +Trace("Loaded error layer.") + +return Def.ActorFrame(frame_args) diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua deleted file mode 100644 index 94615c7876..0000000000 --- a/Themes/_fallback/BGAnimations/ScreenSystemLayer errorbg.lua +++ /dev/null @@ -1,14 +0,0 @@ -return Def.ActorFrame{ - Def.Quad { - Name= "erp", - InitCommand= function(self) - self:setsize(SCREEN_WIDTH, SCREEN_HEIGHT) - self:horizalign(left) - self:vertalign(top) - self:diffuse(color("0,0,0,0")) - self:diffusealpha(.85) - end, - SetCoveredHeightCommand= function(self, param) - end - } -} diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua index f2c09ff8a4..0e638d6afe 100644 --- a/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer overlay/default.lua @@ -64,147 +64,27 @@ t[#t+1] = Def.ActorFrame { CreditsText( PLAYER_2 ); }; -- Text - --- Minor text formatting functions from Kyzentun. --- TODO: Figure out why BitmapText:maxwidth doesn't do what I want. -local function width_limit_text(text, limit, natural_zoom) - natural_zoom= natural_zoom or 1 - if text:GetWidth() * natural_zoom > limit then - text:zoomx(limit / text:GetWidth()) - else - text:zoomx(natural_zoom) - end -end - -local function width_clip_text(text, limit) - local full_text= text:GetText() - local fits= text:GetZoomedWidth() <= limit - local prev_max= #full_text - 1 - local prev_min= 0 - if not fits then - while prev_max - prev_min > 1 do - local new_max= math.round((prev_max + prev_min) / 2) - text:settext(full_text:sub(1, 1+new_max)) - if text:GetZoomedWidth() <= limit then - prev_min= new_max - else - prev_max= new_max - end - end - text:settext(full_text:sub(1, 1+prev_min)) - end -end - -local function width_clip_limit_text(text, limit, natural_zoom) - natural_zoom= natural_zoom or text:GetZoomY() - local text_width= text:GetWidth() * natural_zoom - if text_width > limit * 2 then - text:zoomx(natural_zoom * .5) - width_clip_text(text, limit) - else - width_limit_text(text, limit, natural_zoom) - end -end - -local errorbg -local err_frame -local text_actors= {} -local line_height= 12 -- A good line height for Common Normal at .5 zoom. -local line_width= SCREEN_WIDTH - 20 - -local next_message_actor= 1 - -local min_message_time= { show= 4, hide= .125} -local message_time= { - show= min_message_time.show, hide= min_message_time.hide} - -function SetOverlayMessageTime(t, which) - if not min_message_time[which] then - SCREENMAN:SystemMessage("Attempted to set invalid overlay message time field: " .. tostring(which)) - return - end - if t < min_message_time[which] then - SCREENMAN:SystemMessage( - "Attempted to set overlay message " .. which .. - " time to below minimum of " .. min_message_time[which] .. ".") - return - end - message_time[which]= t -end - -local errbg_actor= LoadActor(THEME:GetPathB("ScreenSystemLayer", "errorbg")) --- Force the name of the returned actor so that we can easily use playcommand on it later. -errbg_actor.Name= "errorbg" - -local frame_args= { - Name="Error frame", - InitCommand= function(self) - err_frame= self - errorbg= self:GetChild("errorbg") - self:y(-SCREEN_HEIGHT) - end, +t[#t+1] = Def.ActorFrame { + Def.Quad { + InitCommand=cmd(zoomtowidth,SCREEN_WIDTH;zoomtoheight,30;horizalign,left;vertalign,top;y,SCREEN_TOP;diffuse,color("0,0,0,0")); + OnCommand=cmd(finishtweening;diffusealpha,0.85;); + OffCommand=cmd(sleep,3;linear,0.5;diffusealpha,0;); + }; + LoadFont("Common","Normal") .. { + Name="Text"; + InitCommand=cmd(maxwidth,750;horizalign,left;vertalign,top;y,SCREEN_TOP+10;x,SCREEN_LEFT+10;shadowlength,1;diffusealpha,0;); + OnCommand=cmd(finishtweening;diffusealpha,1;zoom,0.5); + OffCommand=cmd(sleep,3;linear,0.5;diffusealpha,0;); + }; SystemMessageMessageCommand = function(self, params) - err_frame:stoptweening() - err_frame:visible(true) - local covered_height= line_height * (next_message_actor-1) - err_frame:y(-SCREEN_HEIGHT + covered_height) - if errorbg then - errorbg:playcommand("SetCoveredHeight", {height= covered_height}) + self:GetChild("Text"):settext( params.Message ); + self:playcommand( "On" ); + if params.NoAnimate then + self:finishtweening(); end - err_frame:sleep(message_time.show) - err_frame:queuecommand("DecNextActor") - -- Shift the text on all the actors being shown up by one. - for i= next_message_actor, 1, -1 do - if text_actors[i] then - text_actors[i]:visible(true) - if i > 1 and text_actors[i-1] then - text_actors[i]:settext(text_actors[i-1]:GetText()) - else - text_actors[i]:settext(params.Message) - end - width_clip_limit_text(text_actors[i], line_width) - end - end - if next_message_actor <= #text_actors and not params.NoAnimate then - next_message_actor= next_message_actor + 1 - end - end, - DecNextActorCommand= function(self) - self:linear(message_time.hide) - self:y(self:GetY()-line_height) - if text_actors[next_message_actor] then - text_actors[next_message_actor]:visible(false) - end - next_message_actor= next_message_actor - 1 - if next_message_actor > 1 then - self:queuecommand("DecNextActor") - else - self:queuecommand("Off") - end - end, - OffCommand= cmd(visible,false), - HideSystemMessageMessageCommand = cmd(finishtweening), - errbg_actor, -} --- Create enough text actors that we can fill the screen. -local num_text= SCREEN_HEIGHT / line_height -for i= 1, num_text do - frame_args[#frame_args+1]= LoadFont("Common","Normal") .. { - Name="Text" .. i, - InitCommand= function(self) - -- Put them in the list in reverse order so the ones at the bottom of the screen are used first. - text_actors[num_text-i+1]= self - self:horizalign(left) - self:vertalign(top) - self:x(SCREEN_LEFT + 10) - self:y(SCREEN_TOP + (line_height * (i-1)) + 2) - self:shadowlength(1) - self:zoom(.5) - self:visible(false) - end, - OffCommand= cmd(visible,false), - } -end -t[#t+1] = Def.ActorFrame(frame_args) + self:playcommand( "Off" ); + end; + HideSystemMessageMessageCommand = cmd(finishtweening); +}; -return t +return t; diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 448a35b2e7..0eb3886c49 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -1301,6 +1301,7 @@ Assist=Assist AutoPlay=AutoPlay Autosync=Autosync CPU=CPU +Clear Errors=Clear Errors Clear Profile Stats=Clear Profile Scores CoinMode=CoinMode Debug Menu=Debug Menu @@ -1328,6 +1329,7 @@ Show Masks=Show Masks Slow=Slow Song=Song Tempo=Tempo +Toggle Errors=Toggle Show Errors Uptime=Uptime Visual Delay Down=Visual Delay Down Visual Delay Up=Visual Delay Up diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index 8fe8fda8da..66c3de676d 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -57,7 +57,7 @@ SelectMusicScreen="ScreenSelectMusic" # Screens that show over everything else. # Only mess with this if you know what you're doing. -OverlayScreens="ScreenSystemLayer,ScreenSyncOverlay,ScreenStatsOverlay,ScreenDebugOverlay,ScreenConsoleOverlay,ScreenInstallOverlay" +OverlayScreens="ScreenSystemLayer,ScreenSyncOverlay,ScreenStatsOverlay,ScreenDebugOverlay,ScreenInstallOverlay" # Used in PlayerStageStats for formatting scores. PercentScoreDecimalPlaces=2 @@ -1580,16 +1580,6 @@ CreditsP2ScreenChangedMessageCommand=queuecommand,"UpdateVisible";queuecommand," CreditsP2OnCommand=horizalign,right;vertalign,bottom;zoom,0.675;shadowlength,1; CreditsP2OffCommand= -[ScreenConsoleOverlay] -Class="ScreenSystemLayer" -Fallback="ScreenSystemLayer" -# -CreditsInitCommand=visible,false;zoom,0 -CreditsP1X=-9999 -CreditsP2X=-9999 -CreditsP1Y=-9999 -CreditsP2Y=-9999 - [ScreenInstallOverlay] Class="ScreenInstallOverlay" Fallback="Screen" diff --git a/src/Actor.cpp b/src/Actor.cpp index f3e580dce1..5d96198cf1 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -1458,7 +1458,10 @@ public: return 0; } ITween *pTween = ITween::CreateFromStack( L, 2 ); - p->BeginTweening(fTime, pTween); + if(pTween != NULL) + { + p->BeginTweening(fTime, pTween); + } return 0; } static int stoptweening( T* p, lua_State * ) { p->StopTweening(); return 0; } diff --git a/src/DynamicActorScroller.cpp b/src/DynamicActorScroller.cpp index 2cf6ead5af..b8f4cc7d86 100644 --- a/src/DynamicActorScroller.cpp +++ b/src/DynamicActorScroller.cpp @@ -18,7 +18,15 @@ void DynamicActorScroller::LoadFromNode( const XNode *pNode ) * * Make one extra copy if masking is enabled. */ if( m_SubActors.size() != 1 ) - RageException::Throw( "%s: DynamicActorScroller: loaded %i nodes; require exactly one", ActorUtil::GetWhere(pNode).c_str(), (int)m_SubActors.size() ); + { + LuaHelpers::ReportScriptErrorFmt("%s: DynamicActorScroller: loaded %i nodes; require exactly one", ActorUtil::GetWhere(pNode).c_str(), (int)m_SubActors.size()); + // Remove all but one. + for( size_t i=1; i // conversion for lua functions. #include @@ -801,7 +801,9 @@ void LuaHelpers::ReportScriptError(RString const& Error, RString ErrorType) { short_error= Error.substr(0, line_break_pos); } - SCREENMAN->SystemMessage(short_error); + Message msg("ScriptError"); + msg.SetParam("Message", short_error); + MESSAGEMAN->Broadcast(msg); LOG->Warn(Error.c_str()); Dialog::OK(Error, ErrorType); } diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index 03294998b0..1c442976cb 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -153,7 +153,10 @@ public: // 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() ); + { + LuaHelpers::ReportScriptErrorFmt("Parse error in \"ScreenOptionsMaster::%s\".", sParam.c_str()); + lCmds= ParseCommands("0"); + } m_Def.m_bOneChoiceForAllPlayers = false; const int NumCols = StringToInt( lCmds.v[0].m_vsArgs[0] ); @@ -195,10 +198,20 @@ public: } else { - RageException::Throw( "Unkown row flag \"%s\".", sName.c_str() ); + LuaHelpers::ReportScriptErrorFmt( "Unkown row flag \"%s\".", sName.c_str() ); } } + if(NumCols < 1) + { + LuaHelpers::ReportScriptErrorFmt(sParam + " has %d choices.", NumCols); + GameCommand mc; + mc.ApplyCommitsScreens( false ); + mc.Load( 0, ParseCommands("name,Error") ); + m_aListEntries.push_back(mc); + RString sChoice = mc.m_sName; + m_Def.m_vsChoices.push_back( sChoice ); + } for( int col = 0; col < NumCols; ++col ) { GameCommand mc; @@ -209,17 +222,19 @@ public: if( mc.m_sName == "" && NumCols == 1 ) mc.m_sName = sParam; if( mc.m_sName == "" ) - RageException::Throw( "List \"%s\", col %i has no name.", sParam.c_str(), col ); + { + LuaHelpers::ReportScriptErrorFmt("List \"%s\", col %i has no name.", sParam.c_str(), col); + mc.m_sName= ""; + } if( !mc.IsPlayable() ) { - LOG->Trace( "\"%s\" is not playable.", sParam.c_str() ); + LuaHelpers::ReportScriptErrorFmt("\"%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 ); } @@ -543,7 +558,7 @@ public: } else { - RageException::Throw( "Invalid StepsType param \"%s\".", sParam.c_str() ); + LuaHelpers::ReportScriptErrorFmt("Invalid StepsType param \"%s\".", sParam.c_str()); } m_Def.m_sName = sParam; diff --git a/src/ScreenDebugOverlay.cpp b/src/ScreenDebugOverlay.cpp index e931f38e42..bb323496d7 100644 --- a/src/ScreenDebugOverlay.cpp +++ b/src/ScreenDebugOverlay.cpp @@ -549,6 +549,8 @@ static LocalizedString RESTART ( "ScreenDebugOverlay", "Restart" ); static LocalizedString SCREEN_ON ( "ScreenDebugOverlay", "Send On To Screen" ); static LocalizedString SCREEN_OFF ( "ScreenDebugOverlay", "Send Off To Screen" ); static LocalizedString RELOAD_OVERLAY_SCREENS( "ScreenDebugOverlay", "Reload Overlay Screens" ); +static LocalizedString TOGGLE_ERRORS( "ScreenDebugOverlay", "Toggle Errors" ); +static LocalizedString CLEAR_ERRORS( "ScreenDebugOverlay", "Clear Errors" ); static LocalizedString RELOAD_THEME_AND_TEXTURES( "ScreenDebugOverlay", "Reload Theme and Textures" ); static LocalizedString WRITE_PROFILES ( "ScreenDebugOverlay", "Write Profiles" ); static LocalizedString WRITE_PREFERENCES ( "ScreenDebugOverlay", "Write Preferences" ); @@ -1039,8 +1041,36 @@ class DebugLineReloadOverlayScreens : public IDebugLine virtual RString GetPageName() const { return "Theme"; } virtual void DoAndLog( RString &sMessageOut ) { - IDebugLine::DoAndLog(sMessageOut); SCREENMAN->ReloadOverlayScreensAfterInputFinishes(); + IDebugLine::DoAndLog(sMessageOut); + } +}; + +class DebugLineToggleErrors : public IDebugLine +{ + virtual RString GetDisplayTitle() { return TOGGLE_ERRORS.GetValue(); } + virtual RString GetDisplayValue() { return RString(); } + virtual bool IsEnabled() { return true; } + virtual RString GetPageName() const { return "Theme"; } + virtual void DoAndLog( RString &sMessageOut ) + { + Message msg("ToggleErrors"); + MESSAGEMAN->Broadcast(msg); + IDebugLine::DoAndLog(sMessageOut); + } +}; + +class DebugLineClearErrors : public IDebugLine +{ + virtual RString GetDisplayTitle() { return CLEAR_ERRORS.GetValue(); } + virtual RString GetDisplayValue() { return RString(); } + virtual bool IsEnabled() { return true; } + virtual RString GetPageName() const { return "Theme"; } + virtual void DoAndLog( RString &sMessageOut ) + { + Message msg("ClearErrors"); + MESSAGEMAN->Broadcast(msg); + IDebugLine::DoAndLog(sMessageOut); } }; @@ -1227,6 +1257,8 @@ DECLARE_ONE( DebugLineCurrentScreenOn ); DECLARE_ONE( DebugLineCurrentScreenOff ); DECLARE_ONE( DebugLineReloadTheme ); DECLARE_ONE( DebugLineReloadOverlayScreens ); +DECLARE_ONE( DebugLineToggleErrors ); +DECLARE_ONE( DebugLineClearErrors ); DECLARE_ONE( DebugLineWriteProfiles ); DECLARE_ONE( DebugLineWritePreferences ); DECLARE_ONE( DebugLineMenuTimer ); diff --git a/src/ScreenSystemLayer.cpp b/src/ScreenSystemLayer.cpp index a705cb68df..49ba1637bc 100644 --- a/src/ScreenSystemLayer.cpp +++ b/src/ScreenSystemLayer.cpp @@ -155,8 +155,10 @@ void ScreenSystemLayer::Init() { Screen::Init(); - m_sprOverlay.Load( THEME->GetPathB("ScreenSystemLayer", "overlay") ); + m_sprOverlay.Load( THEME->GetPathB(m_sName, "overlay") ); this->AddChild( m_sprOverlay ); + m_errLayer.Load( THEME->GetPathB(m_sName, "error") ); + this->AddChild( m_errLayer ); } /* diff --git a/src/ScreenSystemLayer.h b/src/ScreenSystemLayer.h index b5a28e931a..08d4295fd2 100644 --- a/src/ScreenSystemLayer.h +++ b/src/ScreenSystemLayer.h @@ -13,6 +13,7 @@ public: private: AutoActor m_sprOverlay; + AutoActor m_errLayer; }; diff --git a/src/Tween.cpp b/src/Tween.cpp index 69021d0e14..99828d29f6 100644 --- a/src/Tween.cpp +++ b/src/Tween.cpp @@ -97,7 +97,10 @@ ITween *ITween::CreateFromStack( Lua *L, int iStackPos ) luaL_checktype( L, iStackPos+1, LUA_TTABLE ); int iArgs = lua_objlen( L, iStackPos+1 ); if( iArgs != 4 && iArgs != 8 ) - RageException::Throw( "CreateFromStack: table argument must have 4 or 8 entries" ); + { + LuaHelpers::ReportScriptErrorFmt("Tween::CreateFromStack: table argument must have 4 or 8 entries"); + return NULL; + } float fC[8]; for( int i = 0; i < iArgs; ++i ) From fb1a251b7a1f5f27094e9d47bb7b21468b2314af Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sat, 12 Jul 2014 14:59:10 -0600 Subject: [PATCH 06/12] Changed many places that used RageException to use ReportScriptError instead. Wrote ScreenOptionsExample.ini as documentation example for the OptionRow system. Rewrote gamecommands.txt to include all GameCommands. Fixed Commands::GetOriginalCommandString to insert the semicolons that separated the original commands. Changed nonsensical boolean |= true statements in GameCommand.cpp to just use =. Added protection to ReportScriptError to keep it from recursing through itself when an error occurs in error reporting. Added UseAbort option to ReportScriptError for places that want to use an AbortRetryIgnore dialog to query the user. Added ScriptErrorMessage for places that need to handle the warning/dialog part separately. Added logging flag to MESSAGEMAN so that all messages broadcast can be logged when desired. Changed OptionRowHandler::LoadInternal to return a boolean success value as part of error handling. --- Docs/Luadoc/Lua.xml | 1 + Docs/Luadoc/LuaDocumentation.xml | 3 + .../Example_Screens/ScreenOptionsExample.ini | 128 +++++++++++++ Docs/Themerdocs/gamecommands.txt | 137 ++++++++++---- .../BGAnimations/ScreenSystemLayer error.lua | 7 +- src/Command.cpp | 6 + src/CommonMetrics.cpp | 18 +- src/CourseContentsList.cpp | 4 +- src/DifficultyIcon.cpp | 4 +- src/DifficultyList.cpp | 22 ++- src/GameCommand.cpp | 58 +++--- src/GameState.cpp | 5 +- src/HoldJudgment.cpp | 4 +- src/LuaManager.cpp | 27 ++- src/LuaManager.h | 13 +- src/MessageManager.cpp | 13 ++ src/MessageManager.h | 3 + src/MeterDisplay.cpp | 9 +- src/OptionRowHandler.cpp | 176 +++++++++--------- src/OptionRowHandler.h | 6 +- src/OptionsList.cpp | 5 +- src/PlayerAI.cpp | 50 +++-- src/ScreenManager.cpp | 27 ++- src/ScreenOptionsMaster.cpp | 6 +- src/ScreenSelect.cpp | 32 ++-- src/ScreenSelectMaster.cpp | 20 +- src/ScreenSelectMusic.cpp | 9 +- src/Sprite.cpp | 4 +- src/ThemeManager.cpp | 42 +++-- 29 files changed, 593 insertions(+), 246 deletions(-) create mode 100644 Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 27c291591a..c2297a38d2 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -925,6 +925,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 77370ffcf2..bee1948b6f 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -2769,6 +2769,9 @@ save yourself some time, copy this for undocumented things: second argument is an optional table of parameters. It may be omitted or explicitly set to nil. + + Sets whether logging of messages is enabled. If log is true, all messages that pass through Broadcast (from the engine for from the theme or from anywhere else), will be logged with Trace. + diff --git a/Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini b/Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini new file mode 100644 index 0000000000..b373987f2c --- /dev/null +++ b/Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini @@ -0,0 +1,128 @@ +# Try out this examply by copying these metrics into your metrics.ini. + +# Some elements will require entries to be added to the language file. Read Examples/LanguageFile.ini for instructions on adding entries to a language file. + +# Setting InitialScreen in the Common section just makes the theme start up in this screen. +# This is only done for ease of seeing this example in action, and is not part of making an options screen. +[Common] +InitialScreen="ScreenOptionsExample" + +# The screen must have a metrics section with its name. +[ScreenOptionsExample] +# The Fallback metric for the screen should be "ScreenOptionsMaster", to pull in necessary fallback metrics from that section. +Fallback="ScreenOptionsMaster" + +# The ForceAllPlayers metric will force join all players if set. +# This example sets it because the example runs when players would not otherwise be joined. +# An options screen that is meant to set song options between song select and gameplay should not set ForceAllPlayers to true. +ForceAllPlayers=true + +# NavigationMode controls how the cursor is used to navigate the screen. +# If it's not set by the theme, it defaults to the setting in Preferences.ini. +# "toggle" sets navigation to the preferred toggle style mode. In toggle mode, all options on all rows are treated as on/off toggled options. +# "menu" sets navigation to the menu style mode. In menu mode, each should have only a single option which sets a new screen when chosen. +# This example does not set NavigationMode because the default is what it wants. +# NavigationMode= + +# InputMode sets the input mode. This controls whether the players have separate cursors. +# "individual" means the players' cursors can be separated and move to different options. +# "together" means both players control the same cursor. +InputMode="individual" + +# LineNames sets the names of the rows on the screen. +# Names are separated by commas and can be anything that doesn't contain a comma. +# Each name will be appended to "Line" to find the metric that defines that line. So a line name of "a" will be defined by the metric "Linea". +LineNames="a,b,c,d,e,f,g,h,i,j,k,l,m,n" + +# Each line is defined by a metric that sets its type and arguments for that type. +# This example is going to include a simple example of each type. +# The types of option rows are: "list", "lua", "conf", "stepstype", "steps", "gamecommand". + +# The "list" row type takes a single argument: The subtype, or the metric that defines the elements of the list. +# The subtypes of list are: "NoteSkins", "Steps", "StepsLocked", "Characters", "Styles", "Groups", "Difficulties", "SongsInCurrentSongGroup". +# If the subtype is not one of the above, it is the name of the metric that defines the list. + +# This list example will create a list defined by a metric. +# A list that is defined by a metric requires entries in the language file for the option title and the option explanation. +# "list,Examples" requires an "Examples" entry in the "OptionTitles" and "OptionExplanations" sections of the language file. +# When looking for the metric that defines the list, the ScreenOptionsMaster section is searched. See the ScreenOptionsMaster section of this file for an explanation of the body of the list. +Linea="list,Examples" + +# The other list subtypes construct lists of GameCommands that execute specific actions. + +# The "NoteSkins" subtype constructs a list out of all noteskins available. +Lineb="list,NoteSkins" + +# The "Steps" subtype constructs a list out of the playable steps for the current song or course. Because this is an example screen and there is no current song set, no steps are in the list when this screen is used. +Linec="list,Steps" +# The "StepsLocked" subtype is identical to the "Steps" subtype, but forces all players to make the same choice. +Lined="list,StepsLocked" + +# The "Characters" subtype constructs a list out of the selectable dancers. +Linee="list,Characters" + +# The "Styles" subtype constructs a list ouf of all styles for the current game type. +Linef="list,Styles" + +# The "Groups" subtype constructs a list out of all the song groups. +Lineg="list,Groups" + +# The "Difficulties" subtype constructs a list of the difficulties in the "DifficultiesToShow" metric from the "Common" section. +Lineh="list,Difficulties" + +# The "SongsInCurrentSongGroup" subtype +Linei="list,SongsInCurrentSongGroup" + +# This is the end of the list subtype examples. + +# The "lua" row type takes a single argument, a chunk of lua to run which must return a table defining the row. Full explanation of the lua option row type can be found in Examples/OptionRowHandlerLua.lua. +# This example uses the "ArbitrarySpeedMods()" function just because I like it. +Linej="lua,ArbitrarySpeedMods()" + +# The "steps" row type creates choices for the different charts for the current song. It takes a single argument, "EditSteps" or "EditSourceSteps". Its purpose is currently unknown. +Linek="steps,EditSteps" + +# The "stepstype" row type is related to the "steps" row type and takes the same argument. Its purpose is also unknown. +Linel="stepstype,EditSteps" + +# The "conf" row type constructs a row for changing a conf option. conf options are the options in Preferences.ini and the argument is the name of the option to edit. +Linem="conf,AllowW1" + +# The "gamecommand" row type constructs a row with a single choice from a GameCommand and applies that GameCommand when the choice is chosen. +# Everything after the "gamecommand;" part of the row command is used to construct the GameCommand. +# Full documention on GameCommands can be found in gamecommands.txt. +# This example just makes a GameCommand that does nothing, but has a name. +Linen="gamecommand;name,ExampleCommand" + + +[ScreenOptionsMaster] +# The main metric defining the list is a command that sets the number of elements and some flags. All flags are optional, and most commonly will not be used. +# The number of elements must come first. +# Flags are separated by semicolons. +# Arguments to flags are separated by commas. +# Flags are: +# "together": Any choice chosen is applied to both players. +# "selectmultiple": Multiple choices on the row can be chosen. +# "selectone": One choice on the row can be chosen. +# "selectnone": No choice on the row can be chosen. +# selectmultiple, selectone, and selectnone are mutually exclusive. If more than one is set, only the last one applies. +# "showoneinrow": Only show one choice in the row. +# "default": Sets the default choice. Takes one argument, the number of the choice. "default,2" makes the second choice the default. +# "reloadrowmessages": Sets the messages that will cause the row to be reloaded. Arguments are the messages. +# "enabledforplayers": Sets which players are allowed to use the row. Arguments are numbers. +# "exportonchange": Sets whether the option is exported every time it changes. +# "broadcastonexport": Sets the messages that are broadcast when the row is exported. Arguments are the messages. +# Example line using all flags: +#Examples="4;together;selectmultiple;showoneinrow;default,3;reloadrowmessages,ExampleReload;enabledforplayers,1,2;exportonchange;broadcastonexport,ExampleExport" +Examples="4" + +# Each choice in the list is defined by a metric. The name of the metric is the name of the list with a comma and the id of the choice. +# So the first choice in our "Examples" list is defined by the metric "Examples,1". +# The metric defining a choice is a GameCommand. Full explanation of GameCommands is beyond the scope of this example. This example will only use the "name" part of a GameCommand. See gamecommands.txt for full documentation on GameCommands. +# The "name" part of a GameCommand sets the name for that GameCommand. There must be an entry for this name in the "OptionNames" section of the language file. +# A list also needs a Default entry, a command to apply if the none of the choices are selected. It can be empty, and it does not need a name. +ExamplesDefault="" +Examples,1="name,Ex1" +Examples,2="name,Ex2" +Examples,3="name,Ex3" +Examples,4="name,Ex4" diff --git a/Docs/Themerdocs/gamecommands.txt b/Docs/Themerdocs/gamecommands.txt index 8b2f11a277..bed2c60725 100644 --- a/Docs/Themerdocs/gamecommands.txt +++ b/Docs/Themerdocs/gamecommands.txt @@ -1,62 +1,123 @@ -all the known gamecommands (pre-sm-ssc) +GameCommands are constructed from subcommands separated by semicolons. +Subcommands are constructed from a command name and its arguments, separated by commas. +Each command takes its arguments and interprets them to set a property of the GameCommand. + +Most of the things that can be done through GameCommands can be done better through other means. GameCommand is kept around for compatibility with older versions and because ScreenOptionsMaster based screens often don't have the ability to use the better ways of doing things for their choices. + +Example command: +"name,Example;screen,ScreenInit" +This example creates a GameCommand with the name "Example" and its screen property set to "ScreenInit". + +When a GameCommand is applied, its properties are used to change things about the game. + +The various properties that can be set for a GameCommand are listed below, with their effects when applied. + +"announcer" +Sets the name of the announcer. The argument must be the name of the directoory of the announcer. + +"applydefaultoptions" +Applies the default options to the player. No argument. + +"clearcredits" +Clears any credits that have been inserted. No argument. + +"course" +Sets the current course to the argument. -"style" -"playmode" "difficulty" +Sets the difficulty property of this GameCommand. +Acceptable args are: +Beginner, Easy, Medium, Hard, Challenge, Edit -"announcer" {m_sAnnouncer = sValue;} +"fademusic" +Must have 2 args: The volume to fade out to and the seconds to spread the fade over. -"name" {m_sName = sValue;} +"goalcalories" +Sets the calorie goal of the player. -"text" {m_sText = sValue;} +"goaltype" +Sets the goal type of the player. Types are: Calories, Time, None. -"mod" {m_sPreferredModifiers += sValue;} - -"stagemod" {m_sStageModifiers += sValue;} +"insertcredit" +Inserts a credit. No arguments. "lua" +Sets a lua function to be run when this GameCommand is applied. -"screen" {m_sScreen = sValue;} +"mod" +Sets a modifier to be applied to the player(s). + +"name" +Sets the name of this GameCommand. For GameCommands used in option rows as choices, this is used to set the text that is shown on screen for the choice. + +"playmode" +The "playmode" game command controls what mode of play the game is in. This is a choice between the normal play mode and the various course or battle modes. The noral effects of the different playmodes are described in playmods.txt, though a theme can check the current playmode and do anything it pleases with that information. +Acceptable args are: +Regular, Nonstop, Oni, Endless, Battle, Rave + +"preparescreen" +Prepares the screen named by the argument when the command is applied. PrepareScreen takes time and blocks graphics, so this should not be used for screens that load a lot of data. + +"profileid" +Sets the default local profile ID of the player. + +"screen" +Sets the screen that will be transitioned to when this GameCommand is applied. + +"setenv" +Arguments are "key,value" pairs of environment variables to be set. Only capable of setting string values. + +"setpref" +Must have 2 args: The preference and the value to set it to. Preferences are listed in Preferences.ini. "song" -"steps" -"course" -"trail" +Sets the current song. The argument should be of the form "Group/SongName". -"setenv" { - if( cmd.m_vsArgs.size() == 3 ) - m_SetEnv[ cmd.m_vsArgs[1] ] = cmd.m_vsArgs[2]; } - -"songgroup" {m_sSongGroup = sValue;} +"songgroup" +Sets the preferred song group. Does not immediately change the group on the music wheel, but the preferred group is the group that will be open when ScreenSelectMusic starts. "sort" -{ - m_SortOrder = StringToSortOrder( sValue ); - if( m_SortOrder == SortOrder_Invalid ) - { - m_sInvalidReason = ssprintf( "SortOrder \"%s\" is not valid.", sValue.c_str() ); - m_bInvalid |= true; - } -} +Sets the sort order. Does not immediately change the sort on the music wheel, but the sort is the sort that will be used when ScreenSelectMusic starts. +Sort names are: +Preferred, Group, Title, BPM, Popularity, TopGrades, Artist, Genre, BeginnerMeter, EasyMeter, MediumMeter, HardMeter, ChallengeMeter, DoubleBeginnerMeter, DoubleEasyMeter, DoubleMediumMeter, DoubleHardMeter, DoubleChallengeMeter, ModeMenu, AllCourses, Nonstop, Oni, Endless, Length, Roulette, Recent. -"weight" {m_iWeightPounds = atoi( sValue );} +"sound" +Plays the sound at the path once. -"goalcalories" {m_iGoalCalories = atoi( sValue );} +"stagemod" +Sets a modifier to be applied to the stagemods. -"goaltype" {m_GoalType = StringToGoalType( sValue );} +"steps" +Sets the current steps to the steps at the specified difficulty for the current song. Emits an error if the song or the style is not already set when the GameCommand is parsed. -"profileid" {m_sProfileID = sValue;} +"stopmusic" +Stops the music that is currently playing. No argument. -"url" {m_sUrl = sValue;} +"style" +The "style" command is used to set the style of the game. The style of the game controls how many pads/players are used. Acceptable args depend on the game type. +For dance: single, versus, double, couple, solo, couple-edit, threepanel, routine +For pump: single, versus, halfdouble, double, couple, couple-edit, routine +For kb7: single, versus +For ez2: single, real, versus, versusReal, double +For para: single, versus +For ds3ddx: single +For beat: single5, versus5, double5, single7, versus7, double7 +For maniax: single, versus, double +For techno: single4, single5, single8, versus4, versus5, versus8, double4, double5, double8 +For popn: popn-five, popn-nine +For lights: cabinet -"sound" {m_sSoundPath = sValue;} +"text" +Sets the text property of the GameCommand. This is an alternative to the name field used by some screens. -"preparescreen" {m_vsScreensToPrepare.push_back( sValue );} +"trail" +Sets the current trail to the trail at the specified difficulty for the current course. Emits an error if the course or the style is not already set when the GameCommand is parsed. -"insertcredit" {m_bInsertCredit = true;} +"url" +Exits the game and launches a browser to visit the url. -"clearcredits" {m_bClearCredits = true;} +"urlnoexit" +Launches a browser to visit the url. -"stopmusic" {m_bStopMusic = true;} - -"applydefaultoptions" {m_bApplyDefaultOptions = true;} \ No newline at end of file +"weight" +Sets the weight of the player. diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua index 4a675d2875..fed9f3808b 100644 --- a/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua @@ -77,7 +77,6 @@ function SetErrorMessageTime(which, t) " time to below minimum of " .. min_message_time[which] .. "."}) return end - Trace("Setting error message " .. which .. " time to " .. t) message_time[which]= t end @@ -113,7 +112,11 @@ local frame_args= { if i > 1 and text_actors[i-1] then text_actors[i]:settext(text_actors[i-1]:GetText()) else - text_actors[i]:settext(params.Message) + -- Someone long ago decided that it was a good idea for "::" to be + -- a synonym for "\n", so that strings in metrics could have line + -- breaks. + -- So replace "::" with ":" so we don't have unwanted line breaks. + text_actors[i]:settext(params.Message:gsub("::", ":")) end width_clip_limit_text(text_actors[i], line_width) end diff --git a/src/Command.cpp b/src/Command.cpp index a0332f037b..cb004a53f2 100644 --- a/src/Command.cpp +++ b/src/Command.cpp @@ -83,7 +83,13 @@ RString Commands::GetOriginalCommandString() const { RString s; FOREACH_CONST( Command, v, c ) + { + if(s != "") + { + s += ";"; + } s += c->GetOriginalCommandString(); + } return s; } diff --git a/src/CommonMetrics.cpp b/src/CommonMetrics.cpp index 3635dd72a1..c8752192d8 100644 --- a/src/CommonMetrics.cpp +++ b/src/CommonMetrics.cpp @@ -44,8 +44,13 @@ void ThemeMetricDifficultiesToShow::Read() { Difficulty d = StringToDifficulty( *i ); if( d == Difficulty_Invalid ) - RageException::Throw( "Unknown difficulty \"%s\" in CourseDifficultiesToShow.", i->c_str() ); - m_v.push_back( d ); + { + LuaHelpers::ReportScriptErrorFmt("Unknown difficulty \"%s\" in CourseDifficultiesToShow.", i->c_str()); + } + else + { + m_v.push_back( d ); + } } } const vector& ThemeMetricDifficultiesToShow::GetValue() const { return m_v; } @@ -74,8 +79,13 @@ void ThemeMetricCourseDifficultiesToShow::Read() { CourseDifficulty d = StringToDifficulty( *i ); if( d == Difficulty_Invalid ) - RageException::Throw( "Unknown CourseDifficulty \"%s\" in CourseDifficultiesToShow.", i->c_str() ); - m_v.push_back( d ); + { + LuaHelpers::ReportScriptErrorFmt("Unknown CourseDifficulty \"%s\" in CourseDifficultiesToShow.", i->c_str()); + } + else + { + m_v.push_back( d ); + } } } const vector& ThemeMetricCourseDifficultiesToShow::GetValue() const { return m_v; } diff --git a/src/CourseContentsList.cpp b/src/CourseContentsList.cpp index 2566465c15..8475b05547 100644 --- a/src/CourseContentsList.cpp +++ b/src/CourseContentsList.cpp @@ -26,7 +26,9 @@ void CourseContentsList::LoadFromNode( const XNode* pNode ) const XNode *pDisplayNode = pNode->GetChild( "Display" ); if( pDisplayNode == NULL ) - RageException::Throw( "%s: CourseContentsList: missing the Display child", ActorUtil::GetWhere(pNode).c_str() ); + { + LuaHelpers::ReportScriptErrorFmt("%s: CourseContentsList: missing the Display child", ActorUtil::GetWhere(pNode).c_str()); + } for( int i=0; iGetChild( ssprintf("CursorP%i",pn+1) ); if( pChild == NULL ) - RageException::Throw( "%s: StepsDisplayList: missing the node \"CursorP%d\"", ActorUtil::GetWhere(pNode).c_str(), pn+1 ); - m_Cursors[pn].LoadActorFromNode( pChild, this ); + { + LuaHelpers::ReportScriptErrorFmt("%s: StepsDisplayList: missing the node \"CursorP%d\"", ActorUtil::GetWhere(pNode).c_str(), pn+1); + } + else + { + m_Cursors[pn].LoadActorFromNode( pChild, this ); + } /* Hack: we need to tween cursors both up to down (cursor motion) and visible to * invisible (fading). Cursor motion needs to stoptweening, so multiple motions @@ -62,10 +67,15 @@ void StepsDisplayList::LoadFromNode( const XNode* pNode ) * colors; I think we do need a diffuse color stack ... */ pChild = pNode->GetChild( ssprintf("CursorP%iFrame",pn+1) ); if( pChild == NULL ) - RageException::Throw( "%s: StepsDisplayList: missing the node \"CursorP%dFrame\"", ActorUtil::GetWhere(pNode).c_str(), pn+1 ); - m_CursorFrames[pn].LoadFromNode( pChild ); - m_CursorFrames[pn].AddChild( m_Cursors[pn] ); - this->AddChild( &m_CursorFrames[pn] ); + { + LuaHelpers::ReportScriptErrorFmt("%s: StepsDisplayList: missing the node \"CursorP%dFrame\"", ActorUtil::GetWhere(pNode).c_str(), pn+1); + } + else + { + m_CursorFrames[pn].LoadFromNode( pChild ); + m_CursorFrames[pn].AddChild( m_Cursors[pn] ); + this->AddChild( &m_CursorFrames[pn] ); + } } for( unsigned m = 0; m < m_Lines.size(); ++m ) diff --git a/src/GameCommand.cpp b/src/GameCommand.cpp index dbda6baa2b..02be6d13d0 100644 --- a/src/GameCommand.cpp +++ b/src/GameCommand.cpp @@ -251,7 +251,7 @@ void GameCommand::LoadOne( const Command& cmd ) if( m_pSong == NULL ) { m_sInvalidReason = ssprintf( "Song \"%s\" not found", sValue.c_str() ); - m_bInvalid |= true; + m_bInvalid = true; } } @@ -265,17 +265,23 @@ void GameCommand::LoadOne( const Command& cmd ) Song *pSong = (m_pSong != NULL)? m_pSong:GAMESTATE->m_pCurSong; const Style *pStyle = m_pStyle ? m_pStyle : GAMESTATE->GetCurrentStyle(); if( pSong == NULL || pStyle == NULL ) - RageException::Throw( "Must set Song and Style to set Steps." ); - - Difficulty dc = StringToDifficulty( sSteps ); - if( dc != Difficulty_Edit ) - m_pSteps = SongUtil::GetStepsByDifficulty( pSong, pStyle->m_StepsType, dc ); - else - m_pSteps = SongUtil::GetStepsByDescription( pSong, pStyle->m_StepsType, sSteps ); - if( m_pSteps == NULL ) { - m_sInvalidReason = "steps not found"; - m_bInvalid |= true; + LuaHelpers::ReportScriptError("Must set Song and Style to set Steps."); + m_sInvalidReason = "Song or Style not set"; + m_bInvalid = true; + } + else + { + Difficulty dc = StringToDifficulty( sSteps ); + if( dc != Difficulty_Edit ) + m_pSteps = SongUtil::GetStepsByDifficulty( pSong, pStyle->m_StepsType, dc ); + else + m_pSteps = SongUtil::GetStepsByDescription( pSong, pStyle->m_StepsType, sSteps ); + if( m_pSteps == NULL ) + { + m_sInvalidReason = "steps not found"; + m_bInvalid = true; + } } } } @@ -286,7 +292,7 @@ void GameCommand::LoadOne( const Command& cmd ) if( m_pCourse == NULL ) { m_sInvalidReason = ssprintf( "Course \"%s\" not found", sValue.c_str() ); - m_bInvalid |= true; + m_bInvalid = true; } } @@ -300,16 +306,22 @@ void GameCommand::LoadOne( const Command& cmd ) Course *pCourse = (m_pCourse != NULL)? m_pCourse:GAMESTATE->m_pCurCourse; const Style *pStyle = m_pStyle ? m_pStyle : GAMESTATE->GetCurrentStyle(); if( pCourse == NULL || pStyle == NULL ) - RageException::Throw( "Must set Course and Style to set Steps." ); - - const CourseDifficulty cd = StringToDifficulty( sTrail ); - ASSERT_M( cd != Difficulty_Invalid, ssprintf("Invalid difficulty '%s'", sTrail.c_str()) ); - - m_pTrail = pCourse->GetTrail( pStyle->m_StepsType, cd ); - if( m_pTrail == NULL ) { - m_sInvalidReason = "trail not found"; - m_bInvalid |= true; + LuaHelpers::ReportScriptError("Must set Course and Style to set Trail."); + m_sInvalidReason = "Course or Style not set"; + m_bInvalid = true; + } + else + { + const CourseDifficulty cd = StringToDifficulty( sTrail ); + ASSERT_M( cd != Difficulty_Invalid, ssprintf("Invalid difficulty '%s'", sTrail.c_str()) ); + + m_pTrail = pCourse->GetTrail( pStyle->m_StepsType, cd ); + if( m_pTrail == NULL ) + { + m_sInvalidReason = "trail not found"; + m_bInvalid = true; + } } } } @@ -331,7 +343,7 @@ void GameCommand::LoadOne( const Command& cmd ) if( m_SortOrder == SortOrder_Invalid ) { m_sInvalidReason = ssprintf( "SortOrder \"%s\" is not valid.", sValue.c_str() ); - m_bInvalid |= true; + m_bInvalid = true; } } @@ -406,7 +418,7 @@ void GameCommand::LoadOne( const Command& cmd ) if( pPref == NULL ) { m_sInvalidReason = ssprintf("unknown preference \"%s\"", cmd.m_vsArgs[1].c_str() ); - m_bInvalid |= true; + m_bInvalid = true; } pPref->FromString(cmd.m_vsArgs[2]); } diff --git a/src/GameState.cpp b/src/GameState.cpp index ba4da3ce06..0436e65a43 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -222,7 +222,10 @@ void GameState::ApplyGameCommand( const RString &sCommand, PlayerNumber pn ) RString sWhy; if( !m.IsPlayable(&sWhy) ) - RageException::Throw( "Can't apply mode \"%s\": %s", sCommand.c_str(), sWhy.c_str() ); + { + LuaHelpers::ReportScriptErrorFmt("Can't apply mode \"%s\": %s", sCommand.c_str(), sWhy.c_str()); + return; + } if( pn == PLAYER_INVALID ) m.ApplyToAllPlayers(); diff --git a/src/HoldJudgment.cpp b/src/HoldJudgment.cpp index bfd0f04a1f..eefa50632f 100644 --- a/src/HoldJudgment.cpp +++ b/src/HoldJudgment.cpp @@ -28,7 +28,9 @@ void HoldJudgment::LoadFromNode( const XNode* pNode ) { RString sFile; if( ActorUtil::GetAttrPath(pNode, "File", sFile) ) - RageException::Throw( "%s: HoldJudgment: missing the attribute \"File\"", ActorUtil::GetWhere(pNode).c_str() ); + { + LuaHelpers::ReportScriptErrorFmt("%s: HoldJudgment: missing the attribute \"File\"", ActorUtil::GetWhere(pNode).c_str()); + } CollapsePath( sFile ); diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index 19827f0223..e76a7d8321 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -47,6 +47,8 @@ namespace LuaHelpers template<> bool FromStack( Lua *L, float &Object, int iOffset ); template<> bool FromStack( Lua *L, int &Object, int iOffset ); template<> bool FromStack( Lua *L, RString &Object, int iOffset ); + + bool InReportScriptError= false; } void LuaManager::SetGlobal( const RString &sName, int val ) @@ -789,7 +791,14 @@ bool LuaHelpers::LoadScript( Lua *L, const RString &sScript, const RString &sNam return true; } -void LuaHelpers::ReportScriptError(RString const& Error, RString ErrorType) +void LuaHelpers::ScriptErrorMessage(RString const& Error) +{ + Message msg("ScriptError"); + msg.SetParam("Message", Error); + MESSAGEMAN->Broadcast(msg); +} + +Dialog::Result LuaHelpers::ReportScriptError(RString const& Error, RString ErrorType, bool UseAbort) { size_t line_break_pos= Error.find('\n'); RString short_error; @@ -801,11 +810,21 @@ void LuaHelpers::ReportScriptError(RString const& Error, RString ErrorType) { short_error= Error.substr(0, line_break_pos); } - Message msg("ScriptError"); - msg.SetParam("Message", short_error); - MESSAGEMAN->Broadcast(msg); + // Protect from a recursion loop resulting from a mistake in the error reporting lua. + if(!InReportScriptError) + { + InReportScriptError= true; + ScriptErrorMessage(short_error); + InReportScriptError= false; + } LOG->Warn(Error.c_str()); + if(UseAbort) + { + RString with_correct= Error + " Correct this and click Retry, or Cancel to break."; + return Dialog::AbortRetryIgnore(with_correct, ErrorType); + } Dialog::OK(Error, ErrorType); + return Dialog::ok; } // For convenience when replacing uses of LOG->Warn. diff --git a/src/LuaManager.h b/src/LuaManager.h index 8fc8d820aa..644780eb67 100644 --- a/src/LuaManager.h +++ b/src/LuaManager.h @@ -15,6 +15,9 @@ extern "C" #include "../extern/lua-5.1/src/lauxlib.h" } +// For Dialog::Result +#include "arch/Dialog/Dialog.h" + class LuaManager { public: @@ -58,8 +61,14 @@ namespace LuaHelpers * and the stack is unchanged. */ bool LoadScript( Lua *L, const RString &sScript, const RString &sName, RString &sError ); - /* Report the error through the log and on the screen. */ - void ReportScriptError(RString const& Error, RString ErrorType= "LUA_ERROR"); + /* Report the error three ways: Broadcast message, Warn, and Dialog. */ + /* If UseAbort is true, reports the error through Dialog::AbortRetryIgnore + and returns the result. */ + /* If UseAbort is false, reports the error through Dialog::OK and returns + Dialog::ok. */ + Dialog::Result ReportScriptError(RString const& Error, RString ErrorType= "LUA_ERROR", bool UseAbort= false); + // Just the broadcast message part, for things that need to do the rest differently. + void ScriptErrorMessage(RString const& Error); // For convenience when replacing uses of LOG->Warn. void ReportScriptErrorFmt(const char *fmt, ...); diff --git a/src/MessageManager.cpp b/src/MessageManager.cpp index 6f4ffcda29..021b3100cb 100644 --- a/src/MessageManager.cpp +++ b/src/MessageManager.cpp @@ -5,6 +5,7 @@ #include "RageThreads.h" #include "EnumHelper.h" #include "LuaManager.h" +#include "RageLog.h" #include #include @@ -148,6 +149,7 @@ void Message::SetParamFromStack( lua_State *L, const RString &sName ) MessageManager::MessageManager() { + m_Logging= false; // Register with Lua. { Lua *L = LUA->Get(); @@ -198,6 +200,11 @@ void MessageManager::Unsubscribe( IMessageSubscriber* pSubscriber, MessageID m ) void MessageManager::Broadcast( Message &msg ) const { + // GAMESTATE is created before MESSAGEMAN, and has several BroadcastOnChangePtr members, so they all broadcast when they're initialized. + if(this != NULL && m_Logging) + { + LOG->Trace("MESSAGEMAN:Broadcast: %s", msg.GetName().c_str()); + } msg.SetBroadcast(true); LockMut(g_Mutex); @@ -295,10 +302,16 @@ public: p->Broadcast( msg ); return 0; } + static int SetLogging(T* p, lua_State *L) + { + p->SetLogging(lua_toboolean(L, -1)); + return 0; + } LunaMessageManager() { ADD_METHOD( Broadcast ); + ADD_METHOD( SetLogging ); } }; diff --git a/src/MessageManager.h b/src/MessageManager.h index 5a63204a77..9e6c2a19a4 100644 --- a/src/MessageManager.h +++ b/src/MessageManager.h @@ -198,6 +198,9 @@ public: bool IsSubscribedToMessage( IMessageSubscriber* pSubscriber, const RString &sMessage ) const; inline bool IsSubscribedToMessage( IMessageSubscriber* pSubscriber, MessageID message ) const { return IsSubscribedToMessage( pSubscriber, MessageIDToString(message) ); } + void SetLogging(bool set) { m_Logging= set; } + bool m_Logging; + // Lua void PushSelf( lua_State *L ); }; diff --git a/src/MeterDisplay.cpp b/src/MeterDisplay.cpp index d58c8aab2e..389b5311ce 100644 --- a/src/MeterDisplay.cpp +++ b/src/MeterDisplay.cpp @@ -33,8 +33,13 @@ void MeterDisplay::LoadFromNode( const XNode* pNode ) const XNode *pStream = pNode->GetChild( "Stream" ); if( pStream == NULL ) - RageException::Throw( "%s: MeterDisplay: missing the \"Stream\" attribute", ActorUtil::GetWhere(pNode).c_str() ); - m_sprStream.LoadActorFromNode( pStream, this ); + { + LuaHelpers::ReportScriptErrorFmt("%s: MeterDisplay: missing the \"Stream\" attribute", ActorUtil::GetWhere(pNode).c_str()); + } + else + { + m_sprStream.LoadActorFromNode( pStream, this ); + } m_sprStream->SetName( "Stream" ); //LOAD_ALL_COMMANDS( m_sprStream ); this->AddChild( m_sprStream ); diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index 1c442976cb..cefa228f94 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -117,6 +117,18 @@ int OptionRowHandlerUtil::GetOneSelection( const vector &vbSelected ) static LocalizedString OFF ( "OptionRowHandler", "Off" ); +#define ROW_INVALID_IF(condition, message, retval) \ + if(condition) \ + { \ + LuaHelpers::ReportScriptError("Parse error in option row: " message); \ + return retval; \ + } + +#define CHECK_WRONG_NUM_ARGS(num) \ + ROW_INVALID_IF(command.m_vsArgs.size() != num, "Wrong number of args to option row.", false); +#define CHECK_BLANK_ARG \ + ROW_INVALID_IF(sParam.size() == 0, "Blank arg to Steps row.", false); + // begin OptionRow handlers class OptionRowHandlerList : public OptionRowHandler { @@ -135,31 +147,24 @@ public: m_bUseModNameForIcon = false; m_vsBroadcastOnExport.clear(); } - virtual void LoadInternal( const Commands &cmds ) + virtual bool 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 ) - { - LuaHelpers::ReportScriptErrorFmt("Parse error in \"ScreenOptionsMaster::%s\".", sParam.c_str()); - lCmds= ParseCommands("0"); - } + ROW_INVALID_IF(lCmds.v.size() < 1, "Row command is empty.", false); m_Def.m_bOneChoiceForAllPlayers = false; + ROW_INVALID_IF(lCmds.v[0].m_vsArgs.size() != 1, "Row command has invalid args to number of entries.", false); const int NumCols = StringToInt( lCmds.v[0].m_vsArgs[0] ); + ROW_INVALID_IF(NumCols < 1, "Not enough entries in list.", false); for( unsigned i=1; i &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const { @@ -388,7 +384,7 @@ static void SortNoteSkins( vector &asSkinNames ) class OptionRowHandlerListNoteSkins : public OptionRowHandlerList { - virtual void LoadInternal( const Commands & ) + virtual bool LoadInternal( const Commands & ) { m_Def.m_sName = "NoteSkins"; m_Def.m_bOneChoiceForAllPlayers = false; @@ -408,13 +404,14 @@ class OptionRowHandlerListNoteSkins : public OptionRowHandlerList m_aListEntries.push_back( mc ); m_Def.m_vsChoices.push_back( arraySkinNames[skin] ); } + return true; } }; // XXX: very similar to OptionRowHandlerSteps class OptionRowHandlerListSteps : public OptionRowHandlerList { - virtual void LoadInternal( const Commands & ) + virtual bool LoadInternal( const Commands & ) { m_Def.m_sName = "Steps"; m_Def.m_bAllowThemeItems = false; // we theme the text ourself @@ -423,6 +420,7 @@ class OptionRowHandlerListSteps : public OptionRowHandlerList // don't call default // OptionRowHandlerList::LoadInternal( cmds ); + return true; } virtual ReloadChanged Reload() @@ -533,13 +531,12 @@ public: m_vDifficulties.clear(); } - virtual void LoadInternal( const Commands &cmds ) + virtual bool 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 ); + CHECK_WRONG_NUM_ARGS(2); + CHECK_BLANK_ARG; if( sParam == "EditSteps" ) { @@ -558,7 +555,7 @@ public: } else { - LuaHelpers::ReportScriptErrorFmt("Invalid StepsType param \"%s\".", sParam.c_str()); + ROW_INVALID_IF(true, "Invalid StepsType param \"" + sParam + "\".", false); } m_Def.m_sName = sParam; @@ -620,6 +617,7 @@ public: if( m_pDifficultyToFill ) m_pDifficultyToFill->Set( m_vDifficulties[0] ); m_ppStepsToFill->Set( m_vSteps[0] ); + return true; } virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const { @@ -679,7 +677,7 @@ public: class OptionRowHandlerListCharacters: public OptionRowHandlerList { - virtual void LoadInternal( const Commands & ) + virtual bool LoadInternal( const Commands & ) { m_Def.m_bOneChoiceForAllPlayers = false; m_Def.m_bAllowThemeItems = false; @@ -707,12 +705,13 @@ class OptionRowHandlerListCharacters: public OptionRowHandlerList mc.m_pCharacter = pCharacter; m_aListEntries.push_back( mc ); } + return true; } }; class OptionRowHandlerListStyles: public OptionRowHandlerList { - virtual void LoadInternal( const Commands & ) + virtual bool LoadInternal( const Commands & ) { m_Def.m_bOneChoiceForAllPlayers = true; m_Def.m_sName = "Style"; @@ -730,12 +729,13 @@ class OptionRowHandlerListStyles: public OptionRowHandlerList } m_Default.m_pStyle = vStyles[0]; + return true; } }; class OptionRowHandlerListGroups: public OptionRowHandlerList { - virtual void LoadInternal( const Commands & ) + virtual bool LoadInternal( const Commands & ) { m_Def.m_bOneChoiceForAllPlayers = true; m_Def.m_bAllowThemeItems = false; // we theme the text ourself @@ -760,12 +760,13 @@ class OptionRowHandlerListGroups: public OptionRowHandlerList mc.m_sSongGroup = *g; m_aListEntries.push_back( mc ); } + return true; } }; class OptionRowHandlerListDifficulties: public OptionRowHandlerList { - virtual void LoadInternal( const Commands & ) + virtual bool LoadInternal( const Commands & ) { m_Def.m_bOneChoiceForAllPlayers = true; m_Def.m_sName = "Difficulty"; @@ -790,13 +791,14 @@ class OptionRowHandlerListDifficulties: public OptionRowHandlerList mc.m_dc = *d; m_aListEntries.push_back( mc ); } + return true; } }; // XXX: very similar to OptionRowHandlerSongChoices class OptionRowHandlerListSongsInCurrentSongGroup: public OptionRowHandlerList { - virtual void LoadInternal( const Commands & ) + virtual bool LoadInternal( const Commands & ) { const vector &vpSongs = SONGMAN->GetSongs( GAMESTATE->m_sPreferredSongGroup ); @@ -815,6 +817,7 @@ class OptionRowHandlerListSongsInCurrentSongGroup: public OptionRowHandlerList mc.m_pSong = *p; m_aListEntries.push_back( mc ); } + return true; } }; @@ -995,45 +998,25 @@ public: LUA->Release(L); } - virtual void LoadInternal( const Commands &cmds ) + virtual bool 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 ); + RString sParam = command.GetArg(1).s; + CHECK_WRONG_NUM_ARGS(2); + CHECK_BLANK_ARG; 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 ); - m_TableIsSane= SanityCheckTable(L, sLuaFunction); + m_pLuaTable->SetFromExpression( sParam ); + m_TableIsSane= SanityCheckTable(L, sParam); 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; + return false; } m_pLuaTable->PushSelf(L); @@ -1101,6 +1084,7 @@ public: ASSERT( lua_gettop(L) == 0 ); LUA->Release(L); + return m_TableIsSane; } virtual ReloadChanged Reload() @@ -1277,13 +1261,12 @@ public: OptionRowHandler::Init(); m_pOpt = NULL; } - virtual void LoadInternal( const Commands &cmds ) + virtual bool 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 ); + CHECK_WRONG_NUM_ARGS(2); + CHECK_BLANK_ARG; Init(); @@ -1291,12 +1274,7 @@ public: m_Def.m_bOneChoiceForAllPlayers = true; ConfOption *pConfOption = ConfOption::Find( sParam ); - if( pConfOption == NULL ) - { - LuaHelpers::ReportScriptErrorFmt( "Invalid Conf type \"%s\"", sParam.c_str() ); - pConfOption = ConfOption::Find( "Invalid" ); - ASSERT_M( pConfOption != NULL, "ConfOption::Find(Invalid)" ); - } + ROW_INVALID_IF(pConfOption == NULL, "Invalid Conf type \"" + sParam + "\".", false); pConfOption->UpdateAvailableOptions(); @@ -1306,6 +1284,7 @@ public: m_Def.m_bAllowThemeItems = m_pOpt->m_bAllowThemeItems; m_Def.m_sName = m_pOpt->name; + return true; } virtual void ImportOption( OptionRow *, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const { @@ -1361,13 +1340,12 @@ public: m_vStepsTypesToShow.clear(); } - virtual void LoadInternal( const Commands &cmds ) + virtual bool 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 ); + CHECK_WRONG_NUM_ARGS(2); + CHECK_BLANK_ARG; if( sParam == "EditStepsType" ) { @@ -1383,7 +1361,7 @@ public: } else { - RageException::Throw( "Invalid StepsType param \"%s\".", sParam.c_str() ); + ROW_INVALID_IF(true, "Invalid StepsType param \"" + sParam + "\".", false); } m_Def.m_sName = sParam; @@ -1404,6 +1382,7 @@ public: if( *m_pstToFill == StepsType_Invalid ) m_pstToFill->Set( m_vStepsTypesToShow[0] ); + return true; } virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const @@ -1455,19 +1434,20 @@ public: m_gc.Init(); m_gc.ApplyCommitsScreens( false ); } - virtual void LoadInternal( const Commands &cmds ) + virtual bool LoadInternal( const Commands &cmds ) { - ASSERT( cmds.v.size() > 1 ); + ROW_INVALID_IF(cmds.v.size() <= 1, "No args to construct GameCommand.", false); Commands temp = cmds; temp.v.erase( temp.v.begin() ); m_gc.Load( 0, temp ); - ASSERT( !m_gc.m_sName.empty() ); + ROW_INVALID_IF(m_gc.m_sName.empty(), "GameCommand row has no name.", false); 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( "" ); + return true; } virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const { @@ -1501,20 +1481,21 @@ OptionRowHandler* OptionRowHandlerUtil::Make( const Commands &cmds ) { OptionRowHandler* pHand = NULL; - if( cmds.v.size() == 0 ) - return NULL; - + ROW_INVALID_IF(cmds.v.size() == 0, "No commands for constructing row.", NULL); const RString &name = cmds.v[0].GetName(); + ROW_INVALID_IF(name != "gamecommand" && cmds.v.size() != 1, + "Row must be constructed from single command.", NULL); -#define MAKE( type ) { type *p = new type; p->Load( cmds ); pHand = p; } + bool load_succeeded= false; +#define MAKE( type ) { type *p = new type; load_succeeded= 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; + ROW_INVALID_IF(command.m_vsArgs.size() != 2 || !sParam.size(), + "list row command must be 'list,name' or 'list,type'.", NULL); if( sParam.CompareNoCase("NoteSkins")==0 ) MAKE( OptionRowHandlerListNoteSkins ) else if( sParam.CompareNoCase("Steps")==0 ) MAKE( OptionRowHandlerListSteps ) @@ -1535,16 +1516,29 @@ OptionRowHandler* OptionRowHandlerUtil::Make( const Commands &cmds ) else if( name == "stepstype" ) MAKE( OptionRowHandlerStepsType ) else if( name == "steps" ) MAKE( OptionRowHandlerSteps ) else if( name == "gamecommand" ) MAKE( OptionRowHandlerGameCommand ) + else + { + ROW_INVALID_IF(true, "Invalid row type.", NULL); + } - return pHand; + if(load_succeeded) + { + return pHand; + } + return NULL; } OptionRowHandler* OptionRowHandlerUtil::MakeNull() { OptionRowHandler* pHand = NULL; + bool load_succeeded= false; // Part of the MAKE macro, but unused. Commands cmds; MAKE( OptionRowHandlerNull ) - return pHand; + if(load_succeeded) // Just to get rid of the warning for not using it. + { + return pHand; + } + return NULL; } OptionRowHandler* OptionRowHandlerUtil::MakeSimple( const MenuRowDef &mr ) diff --git a/src/OptionRowHandler.h b/src/OptionRowHandler.h index 4f438fc699..f041e8768d 100644 --- a/src/OptionRowHandler.h +++ b/src/OptionRowHandler.h @@ -150,15 +150,15 @@ public: m_Def.Init(); m_vsReloadRowMessages.clear(); } - void Load( const Commands &cmds ) + bool Load( const Commands &cmds ) { Init(); - this->LoadInternal( cmds ); + return this->LoadInternal( cmds ); } RString OptionTitle() const; RString GetThemedItemText( int iChoice ) const; - virtual void LoadInternal( const Commands & ) { } + virtual bool LoadInternal( const Commands & ) { return true; } /* We may re-use OptionRowHandlers. This is called before each use. If the * contents of the row are dependent on external state (for example, the diff --git a/src/OptionsList.cpp b/src/OptionsList.cpp index e7c5c66d56..101b48a0f0 100644 --- a/src/OptionsList.cpp +++ b/src/OptionsList.cpp @@ -218,7 +218,10 @@ void OptionsList::Load( RString sType, PlayerNumber pn ) OptionRowHandler *pHand = OptionRowHandlerUtil::Make( cmds ); if( pHand == NULL ) - RageException::Throw( "Invalid OptionRowHandler '%s' in %s::Line%s", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), sLineName.c_str() ); + { + LuaHelpers::ReportScriptErrorFmt("Invalid OptionRowHandler '%s' in %s::Line%s", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), sLineName.c_str()); + continue; + } m_Rows[sLineName] = pHand; m_asLoadedRows.push_back( sLineName ); diff --git a/src/PlayerAI.cpp b/src/PlayerAI.cpp index 658aa11c5e..b7bf80905e 100644 --- a/src/PlayerAI.cpp +++ b/src/PlayerAI.cpp @@ -41,23 +41,41 @@ void PlayerAI::InitFromDisk() { RString sKey = ssprintf("Skill%d", i); XNode* pNode = ini.GetChild(sKey); - if( pNode == NULL ) - RageException::Throw( "AI.ini: \"%s\" doesn't exist.", sKey.c_str() ); - TapScoreDistribution& dist = g_Distributions[i]; - dist.fPercent[TNS_None] = 0; - bSuccess = pNode->GetAttrValue( "WeightMiss", dist.fPercent[TNS_Miss] ); - ASSERT( bSuccess ); - bSuccess = pNode->GetAttrValue( "WeightW5", dist.fPercent[TNS_W5] ); - ASSERT( bSuccess ); - bSuccess = pNode->GetAttrValue( "WeightW4", dist.fPercent[TNS_W4] ); - ASSERT( bSuccess ); - bSuccess = pNode->GetAttrValue( "WeightW3", dist.fPercent[TNS_W3] ); - ASSERT( bSuccess ); - bSuccess = pNode->GetAttrValue( "WeightW2", dist.fPercent[TNS_W2] ); - ASSERT( bSuccess ); - bSuccess = pNode->GetAttrValue( "WeightW1", dist.fPercent[TNS_W1] ); - ASSERT( bSuccess ); + if( pNode == NULL ) + { + LuaHelpers::ReportScriptErrorFmt("AI.ini: \"%s\" doesn't exist.", sKey.c_str()); + dist.fPercent[TNS_None] = 0; + dist.fPercent[TNS_Miss] = 1; + dist.fPercent[TNS_W5] = 0; + dist.fPercent[TNS_W4] = 0; + dist.fPercent[TNS_W3] = 0; + dist.fPercent[TNS_W2] = 0; + dist.fPercent[TNS_W1] = 0; + } + else + { +#define SET_MALF_IF(condition, tns) \ + if(condition) \ + { \ + LuaHelpers::ReportScriptError("AI weight for " #tns " not set."); \ + dist.fPercent[tns]= 0; \ + } + dist.fPercent[TNS_None] = 0; + bSuccess = pNode->GetAttrValue( "WeightMiss", dist.fPercent[TNS_Miss] ); + SET_MALF_IF(!bSuccess, TNS_Miss); + bSuccess = pNode->GetAttrValue( "WeightW5", dist.fPercent[TNS_W5] ); + SET_MALF_IF(!bSuccess, TNS_W5); + bSuccess = pNode->GetAttrValue( "WeightW4", dist.fPercent[TNS_W4] ); + SET_MALF_IF(!bSuccess, TNS_W4); + bSuccess = pNode->GetAttrValue( "WeightW3", dist.fPercent[TNS_W3] ); + SET_MALF_IF(!bSuccess, TNS_W3); + bSuccess = pNode->GetAttrValue( "WeightW2", dist.fPercent[TNS_W2] ); + SET_MALF_IF(!bSuccess, TNS_W2); + bSuccess = pNode->GetAttrValue( "WeightW1", dist.fPercent[TNS_W1] ); + SET_MALF_IF(!bSuccess, TNS_W1); +#undef SET_MALF_IF + } float fSum = 0; for( int j=0; jGetName() ); - pScreen->BeginScreen(); - g_OverlayScreens.push_back( pScreen ); + if(pScreen) + { + LuaThreadVariable var2( "LoadingScreen", pScreen->GetName() ); + pScreen->BeginScreen(); + g_OverlayScreens.push_back( pScreen ); + } } // reload song manager colors (to avoid crashes) -aj @@ -323,9 +326,12 @@ void ScreenManager::ReloadOverlayScreens() for( unsigned i=0; iGetName() ); - pScreen->BeginScreen(); - g_OverlayScreens.push_back( pScreen ); + if(pScreen) + { + LuaThreadVariable var2( "LoadingScreen", pScreen->GetName() ); + pScreen->BeginScreen(); + g_OverlayScreens.push_back( pScreen ); + } } this->RefreshCreditsMessages(); @@ -552,7 +558,10 @@ Screen* ScreenManager::MakeNewScreen( const RString &sScreenName ) map::iterator iter = g_pmapRegistrees->find( sClassName ); if( iter == g_pmapRegistrees->end() ) - RageException::Throw( "Screen \"%s\" has an invalid class \"%s\".", sScreenName.c_str(), sClassName.c_str() ); + { + LuaHelpers::ReportScriptErrorFmt("Screen \"%s\" has an invalid class \"%s\".", sScreenName.c_str(), sClassName.c_str()); + return NULL; + } this->ZeroNextUpdate(); @@ -571,6 +580,10 @@ void ScreenManager::PrepareScreen( const RString &sScreenName ) return; Screen* pNewScreen = MakeNewScreen(sScreenName); + if(pNewScreen == NULL) + { + return; + } { LoadedScreen ls; diff --git a/src/ScreenOptionsMaster.cpp b/src/ScreenOptionsMaster.cpp index 662885dbd5..ea2aee1428 100644 --- a/src/ScreenOptionsMaster.cpp +++ b/src/ScreenOptionsMaster.cpp @@ -29,7 +29,9 @@ void ScreenOptionsMaster::Init() vector asLineNames; split( LINE_NAMES, ",", asLineNames ); if( asLineNames.empty() ) - RageException::Throw( "\"%s::LineNames\" is empty.", m_sName.c_str() ); + { + LuaHelpers::ReportScriptErrorFmt("\"%s:LineNames\" is empty.", m_sName.c_str()); + } if( FORCE_ALL_PLAYERS ) { @@ -59,7 +61,7 @@ void ScreenOptionsMaster::Init() OptionRowHandler *pHand = OptionRowHandlerUtil::Make( cmds ); if( pHand == NULL ) { - LuaHelpers::ReportScriptErrorFmt("Invalid OptionRowHandler \"%s\" in \"%s::Line%i\".", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), i); + LuaHelpers::ReportScriptErrorFmt("Invalid OptionRowHandler \"%s\" in \"%s:Line:%s\".", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), sLineName.c_str()); } else { diff --git a/src/ScreenSelect.cpp b/src/ScreenSelect.cpp index bed9829df2..58c046bf27 100644 --- a/src/ScreenSelect.cpp +++ b/src/ScreenSelect.cpp @@ -52,8 +52,10 @@ void ScreenSelect::Init() } } - if( !m_aGameCommands.size() ) - RageException::Throw( "Screen \"%s\" does not set any choices.", m_sName.c_str() ); + if(m_aGameCommands.empty()) + { + LuaHelpers::ReportScriptErrorFmt("Screen \"%s\" does not set any choices.", m_sName.c_str()); + } } void ScreenSelect::BeginScreen() @@ -157,25 +159,27 @@ void ScreenSelect::HandleScreenMessage( const ScreenMessage SM ) } } - if( bAllPlayersChoseTheSame ) + if(!m_aGameCommands.empty()) { - const GameCommand &gc = m_aGameCommands[iMastersIndex]; - m_sNextScreen = gc.m_sScreen; - if( !gc.m_bInvalid ) - gc.ApplyToAllPlayers(); - } - else - { - FOREACH_HumanPlayer( p ) + if( bAllPlayersChoseTheSame ) { - int iIndex = this->GetSelectionIndex(p); - const GameCommand &gc = m_aGameCommands[iIndex]; + const GameCommand &gc = m_aGameCommands[iMastersIndex]; m_sNextScreen = gc.m_sScreen; if( !gc.m_bInvalid ) + gc.ApplyToAllPlayers(); + } + else + { + FOREACH_HumanPlayer( p ) + { + int iIndex = this->GetSelectionIndex(p); + const GameCommand &gc = m_aGameCommands[iIndex]; + m_sNextScreen = gc.m_sScreen; + if( !gc.m_bInvalid ) gc.Apply( p ); + } } } - StopTimer(); SCREENMAN->RefreshCreditsMessages(); diff --git a/src/ScreenSelectMaster.cpp b/src/ScreenSelectMaster.cpp index 0d3b8fd6c6..5aa4c15423 100644 --- a/src/ScreenSelectMaster.cpp +++ b/src/ScreenSelectMaster.cpp @@ -369,7 +369,7 @@ void ScreenSelectMaster::UpdateSelectableChoices() * If any options are playable, make sure one is selected. */ FOREACH_HumanPlayer( p ) { - if( !m_aGameCommands[m_iChoice[p]].IsPlayable() ) + if(!m_aGameCommands.empty() && !m_aGameCommands[m_iChoice[p]].IsPlayable()) Move( p, MenuDir_Auto ); } } @@ -402,7 +402,7 @@ bool ScreenSelectMaster::Move( PlayerNumber pn, MenuDir dir ) if( seen.find(iSwitchToIndex) != seen.end() ) return false; // went full circle and none found seen.insert( iSwitchToIndex ); - } while( !m_aGameCommands[iSwitchToIndex].IsPlayable() && !DO_SWITCH_ANYWAYS ); + } while(!m_aGameCommands.empty() && !m_aGameCommands[iSwitchToIndex].IsPlayable() && !DO_SWITCH_ANYWAYS); return ChangeSelection( pn, dir, iSwitchToIndex ); } @@ -821,22 +821,28 @@ bool ScreenSelectMaster::MenuStart( const InputEventPlus &input ) return true; } - const GameCommand &mc = m_aGameCommands[m_iChoice[pn]]; + GameCommand empty_mc; + // This is so we can avoid having problems when the GameCommands in the choices were all invalid or didn't load or similar. -Kyz + GameCommand *mc= &empty_mc; + if(!m_aGameCommands.empty()) + { + mc = &(m_aGameCommands[m_iChoice[pn]]); + } /* If no options are playable, then we're just waiting for one to become available. * If any options are playable, then the selection must be playable. */ if( !AnyOptionsArePlayable() ) return false; - SOUND->PlayOnceFromDir( ANNOUNCER->GetPathTo(ssprintf("%s comment %s",m_sName.c_str(), mc.m_sName.c_str())) ); + SOUND->PlayOnceFromDir( ANNOUNCER->GetPathTo(ssprintf("%s comment %s",m_sName.c_str(), mc->m_sName.c_str())) ); // Play a copy of the sound, so it'll finish playing even if we leave the screen immediately. - if( mc.m_sSoundPath.empty() && !m_bDoubleChoiceNoSound ) + if( mc->m_sSoundPath.empty() && !m_bDoubleChoiceNoSound ) m_soundStart.PlayCopy(); - if( mc.m_sScreen.empty() ) + if( mc->m_sScreen.empty() ) { - mc.ApplyToAllPlayers(); + mc->ApplyToAllPlayers(); // We want to be able to broadcast a Start message to the theme, in // case a themer wants to handle something. -aj Message msg( MessageIDToString((MessageID)(Message_MenuStartP1+pn)) ); diff --git a/src/ScreenSelectMusic.cpp b/src/ScreenSelectMusic.cpp index c8c7644f44..fa3e5d870f 100644 --- a/src/ScreenSelectMusic.cpp +++ b/src/ScreenSelectMusic.cpp @@ -239,7 +239,14 @@ void ScreenSelectMusic::BeginScreen() } if( GAMESTATE->GetCurrentStyle() == NULL ) - RageException::Throw( "The Style has not been set. A theme must set the Style before loading ScreenSelectMusic." ); + { + LuaHelpers::ReportScriptError("The Style has not been set. A theme must set the Style before loading ScreenSelectMusic."); + // Instead of crashing, set the first compatible style. + vector vst; + GAMEMAN->GetStepsTypesForGame( GAMESTATE->m_pCurGame, vst ); + const Style *pStyle = GAMEMAN->GetFirstCompatibleStyle( GAMESTATE->m_pCurGame, GAMESTATE->GetNumSidesJoined(), vst[0] ); + GAMESTATE->SetCurrentStyle( pStyle ); + } if( GAMESTATE->m_PlayMode == PlayMode_Invalid ) { diff --git a/src/Sprite.cpp b/src/Sprite.cpp index a2e6959f8b..0e67cde16e 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -175,7 +175,7 @@ void Sprite::LoadFromNode( const XNode* pNode ) pFrame->GetAttrValue( "Frame", iFrameIndex ); if( iFrameIndex >= m_pTexture->GetNumFrames() ) - RageException::Throw( "%s: State #%i is frame %d, but the texture \"%s\" only has %d frames", + LuaHelpers::ReportScriptErrorFmt( "%s: State #%i is frame %d, but the texture \"%s\" only has %d frames", ActorUtil::GetWhere(pNode).c_str(), i, iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() ); newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex ); @@ -210,7 +210,7 @@ void Sprite::LoadFromNode( const XNode* pNode ) if( !pNode->GetAttrValue(sFrameKey, iFrameIndex) ) break; if( iFrameIndex >= m_pTexture->GetNumFrames() ) - RageException::Throw( "%s: %s is %d, but the texture \"%s\" only has %d frames", + LuaHelpers::ReportScriptErrorFmt( "%s: %s is %d, but the texture \"%s\" only has %d frames", ActorUtil::GetWhere(pNode).c_str(), sFrameKey.c_str(), iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() ); newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex ); diff --git a/src/ThemeManager.cpp b/src/ThemeManager.cpp index 69ba477d56..b8adc806b5 100644 --- a/src/ThemeManager.cpp +++ b/src/ThemeManager.cpp @@ -669,10 +669,15 @@ bool ThemeManager::GetPathInfoToRaw( PathInfo &out, const RString &sThemeName_, RString message = ssprintf( "ThemeManager: There is more than one theme element that matches " - "'%s/%s/%s'. Please remove all but one of these matches.", + "'%s/%s/%s'. Please remove all but one of these matches: ", sThemeName.c_str(), sCategory.c_str(), MetricsGroupAndElementToFileName(sMetricsGroup,sElement).c_str() ); + message+= asElementPaths[1]; + for(size_t i= 1; i < asElementPaths.size(); ++i) + { + message+= ", " + asElementPaths[i]; + } - switch( Dialog::AbortRetryIgnore(message) ) + switch( LuaHelpers::ReportScriptError(message, "", true) ) { case Dialog::abort: RageException::Throw( "%s", message.c_str() ); @@ -717,7 +722,7 @@ bool ThemeManager::GetPathInfoToRaw( PathInfo &out, const RString &sThemeName_, "Verify that this redirect is correct.", sPath.c_str(), sNewFileName.c_str()); - switch( Dialog::AbortRetryIgnore(sMessage) ) + switch( LuaHelpers::ReportScriptError(sMessage) ) { case Dialog::retry: ReloadMetrics(); @@ -753,11 +758,9 @@ bool ThemeManager::GetPathInfoToAndFallback( PathInfo &out, ElementCategory cate return false; } - RageException::Throw( "Infinite recursion looking up theme element \"%s\"", - MetricsGroupAndElementToFileName(sMetricsGroup, sElement).c_str() ); - /* Not really reached, but Appple's gcc 4 can't figure that out without - * optimization even though RE:Throw() is correctly annotated. */ - while( true ) {} + LuaHelpers::ReportScriptErrorFmt("Infinite recursion looking up theme element \"%s\"", + MetricsGroupAndElementToFileName(sMetricsGroup, sElement).c_str()); + return false; } bool ThemeManager::GetPathInfo( PathInfo &out, ElementCategory category, const RString &sMetricsGroup_, const RString &sElement_, bool bOptional ) @@ -811,10 +814,14 @@ try_element_again: ReloadMetrics(); goto try_element_again; case Dialog::ignore: - LOG->UserLog( "Theme element", sCategory + '/' + sFileName, - "could not be found in \"%s\" or \"%s\".", - GetThemeDirFromName(m_sCurThemeName).c_str(), - GetThemeDirFromName(SpecialFiles::BASE_THEME_NAME).c_str() ); + { + RString error= ssprintf(sCategory + '/' + sFileName, + "could not be found in \"%s\" or \"%s\".", + GetThemeDirFromName(m_sCurThemeName).c_str(), + GetThemeDirFromName(SpecialFiles::BASE_THEME_NAME).c_str() ); + LOG->UserLog("Theme element", "%s", error.c_str()); + LuaHelpers::ScriptErrorMessage(error); + } // Err? if( sFileName == "_missing" ) @@ -918,7 +925,8 @@ bool ThemeManager::GetMetricRawRecursive( const IniFile &ini, const RString &sMe return false; } - RageException::Throw( "Infinite recursion looking up theme metric \"%s::%s\".", sMetricsGroup.c_str(), sValueName.c_str() ); + LuaHelpers::ReportScriptErrorFmt("Infinite recursion looking up theme metric \"%s::%s\".", sMetricsGroup.c_str(), sValueName.c_str()); + return false; } RString ThemeManager::GetMetricRaw( const IniFile &ini, const RString &sMetricsGroup_, const RString &sValueName_ ) @@ -945,12 +953,12 @@ RString ThemeManager::GetMetricRaw( const IniFile &ini, const RString &sMetricsG else FAIL_M(""); - RString sMessage = ssprintf( "%s \"%s::%s\" is missing. Correct this and click Retry, or Cancel to break.", - sType.c_str(), - sMetricsGroup.c_str(), + RString sMessage = ssprintf( "%s \"%s::%s\" is missing.", + sType.c_str(), + sMetricsGroup.c_str(), sValueName.c_str() ); - switch( Dialog::AbortRetryIgnore(sMessage) ) + switch( LuaHelpers::ReportScriptError(sMessage, "", true) ) { case Dialog::abort: { From 6cf23c4dc3be463d602f77159d25ecbb317329b3 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Mon, 14 Jul 2014 15:41:04 -0600 Subject: [PATCH 07/12] Changed many parts of GameCommand to improve error reporting. Fixed GameCommand setpref to not set when loading the command. Minor fix to ScreenOptionsExample.ini to explain that SongInCurrentSongGroup is unusable. CommonMetrics, CourseContentsList, HoldJudgent, OptionRowHandler all have minor changes to fix crashes on malformed theme data. --- .../Example_Screens/ScreenOptionsExample.ini | 9 +- src/BitmapText.cpp | 19 +- src/CommonMetrics.cpp | 12 +- src/CourseContentsList.cpp | 1 + src/GameCommand.cpp | 163 +++++++++++------- src/GameCommand.h | 3 +- src/HoldJudgment.cpp | 2 +- src/OptionRowHandler.cpp | 19 +- src/ScreenManager.cpp | 6 + src/ScreenManager.h | 2 + 10 files changed, 147 insertions(+), 89 deletions(-) diff --git a/Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini b/Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini index b373987f2c..39e4a0032e 100644 --- a/Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini +++ b/Docs/Themerdocs/Examples/Example_Screens/ScreenOptionsExample.ini @@ -32,7 +32,7 @@ InputMode="individual" # LineNames sets the names of the rows on the screen. # Names are separated by commas and can be anything that doesn't contain a comma. # Each name will be appended to "Line" to find the metric that defines that line. So a line name of "a" will be defined by the metric "Linea". -LineNames="a,b,c,d,e,f,g,h,i,j,k,l,m,n" +LineNames="a,b,c,d,e,f,g,h,j,k,l,m,n" # Each line is defined by a metric that sets its type and arguments for that type. # This example is going to include a simple example of each type. @@ -71,7 +71,8 @@ Lineg="list,Groups" Lineh="list,Difficulties" # The "SongsInCurrentSongGroup" subtype -Linei="list,SongsInCurrentSongGroup" +# This option row type is not actually usable because it requires language file entries for every song, which is impractical. +#Linei="list,SongsInCurrentSongGroup" # This is the end of the list subtype examples. @@ -82,8 +83,8 @@ Linej="lua,ArbitrarySpeedMods()" # The "steps" row type creates choices for the different charts for the current song. It takes a single argument, "EditSteps" or "EditSourceSteps". Its purpose is currently unknown. Linek="steps,EditSteps" -# The "stepstype" row type is related to the "steps" row type and takes the same argument. Its purpose is also unknown. -Linel="stepstype,EditSteps" +# The "stepstype" row type is related to the "steps" row type. It takes a single argument, "EditStepsType" or "EditSourceStepsType". Its purpose is also unknown. +Linel="stepstype,EditStepsType" # The "conf" row type constructs a row for changing a conf option. conf options are the options in Preferences.ini and the argument is the name of the option to edit. Linem="conf,AllowW1" diff --git a/src/BitmapText.cpp b/src/BitmapText.cpp index 95ca19f83a..3fc7525637 100644 --- a/src/BitmapText.cpp +++ b/src/BitmapText.cpp @@ -126,18 +126,21 @@ void BitmapText::LoadFromNode( const XNode* pNode ) ThemeManager::EvaluateString( sAltText ); RString sFont; - if( !ActorUtil::GetAttrPath(pNode, "Font", sFont) && - !ActorUtil::GetAttrPath(pNode, "File", sFont) ) + // The short circuiting loading condition that was here before wasn't short circuiting correctly on all platforms. + if(!ActorUtil::GetAttrPath(pNode, "Font", sFont)) { - if( !pNode->GetAttrValue("Font", sFont) && - !pNode->GetAttrValue("File", sFont) ) // accept "File" for backward compatibility + if(!ActorUtil::GetAttrPath(pNode, "File", sFont)) { - LuaHelpers::ReportScriptErrorFmt( "%s: BitmapText: Font or File attribute not found", - ActorUtil::GetWhere(pNode).c_str() ); - sFont = "Common Normal"; + if( !pNode->GetAttrValue("Font", sFont) && + !pNode->GetAttrValue("File", sFont) ) // accept "File" for backward compatibility + { + LuaHelpers::ReportScriptErrorFmt( "%s: BitmapText: Font or File attribute not found", + ActorUtil::GetWhere(pNode).c_str() ); + sFont = "Common Normal"; + } } - sFont = THEME->GetPathF( "", sFont ); } + sFont = THEME->GetPathF( "", sFont ); LoadFromFont( sFont ); diff --git a/src/CommonMetrics.cpp b/src/CommonMetrics.cpp index c8752192d8..7ca1fa2569 100644 --- a/src/CommonMetrics.cpp +++ b/src/CommonMetrics.cpp @@ -38,7 +38,11 @@ void ThemeMetricDifficultiesToShow::Read() vector v; split( ThemeMetric::GetValue(), ",", v ); - ASSERT( v.size() > 0 ); + if(v.empty()) + { + LuaHelpers::ReportScriptError("DifficultiesToShow must have at least one entry."); + return; + } FOREACH_CONST( RString, v, i ) { @@ -73,7 +77,11 @@ void ThemeMetricCourseDifficultiesToShow::Read() vector v; split( ThemeMetric::GetValue(), ",", v ); - ASSERT( v.size() > 0 ); + if(v.empty()) + { + LuaHelpers::ReportScriptError("CourseDifficultiesToShow must have at least one entry."); + return; + } FOREACH_CONST( RString, v, i ) { diff --git a/src/CourseContentsList.cpp b/src/CourseContentsList.cpp index 8475b05547..43e76e1526 100644 --- a/src/CourseContentsList.cpp +++ b/src/CourseContentsList.cpp @@ -28,6 +28,7 @@ void CourseContentsList::LoadFromNode( const XNode* pNode ) if( pDisplayNode == NULL ) { LuaHelpers::ReportScriptErrorFmt("%s: CourseContentsList: missing the Display child", ActorUtil::GetWhere(pNode).c_str()); + return; } for( int i=0; iGameAndStringToStyle( GAMESTATE->m_pCurGame, sValue ); - if( style ) - m_pStyle = style; - else - m_bInvalid = true; + CHECK_INVALID_VALUE(m_pStyle, style, NULL, style); } else if( sName == "playmode" ) { PlayMode pm = StringToPlayMode( sValue ); - if( pm != PlayMode_Invalid ) - m_pm = pm; - else - m_bInvalid = true; + CHECK_INVALID_VALUE(m_pm, pm, PlayMode_Invalid, playmode); } else if( sName == "difficulty" ) { Difficulty dc = StringToDifficulty( sValue ); - if( dc != Difficulty_Invalid ) - m_dc = dc; - else - m_bInvalid = true; + CHECK_INVALID_VALUE(m_dc, dc, Difficulty_Invalid, difficulty); } else if( sName == "announcer" ) @@ -236,23 +245,20 @@ void GameCommand::LoadOne( const Command& cmd ) m_LuaFunction.SetFromExpression( sValue ); if(m_LuaFunction.IsNil()) { - LuaHelpers::ReportScriptError("Lua error in game command: \"" + sValue + "\" evaluated to nil"); + MAKE_INVALID("Lua error in game command: \"" + sValue + "\" evaluated to nil"); } } else if( sName == "screen" ) { - m_sScreen = sValue; + CHECK_INVALID_COND(m_sScreen, sValue, (!SCREENMAN->IsScreenNameValid(sValue)), ("Screen \"" + sValue + "\" has invalid class.")); } else if( sName == "song" ) { - m_pSong = SONGMAN->FindSong( sValue ); - if( m_pSong == NULL ) - { - m_sInvalidReason = ssprintf( "Song \"%s\" not found", sValue.c_str() ); - m_bInvalid = true; - } + CHECK_INVALID_COND(m_pSong, SONGMAN->FindSong(sValue), + (SONGMAN->FindSong(sValue) == NULL), + (ssprintf("Song \"%s\" not found", sValue.c_str()))); } else if( sName == "steps" ) @@ -266,34 +272,31 @@ void GameCommand::LoadOne( const Command& cmd ) const Style *pStyle = m_pStyle ? m_pStyle : GAMESTATE->GetCurrentStyle(); if( pSong == NULL || pStyle == NULL ) { - LuaHelpers::ReportScriptError("Must set Song and Style to set Steps."); - m_sInvalidReason = "Song or Style not set"; - m_bInvalid = true; + MAKE_INVALID("Must set Song and Style to set Steps."); } else { Difficulty dc = StringToDifficulty( sSteps ); - if( dc != Difficulty_Edit ) - m_pSteps = SongUtil::GetStepsByDifficulty( pSong, pStyle->m_StepsType, dc ); - else - m_pSteps = SongUtil::GetStepsByDescription( pSong, pStyle->m_StepsType, sSteps ); - if( m_pSteps == NULL ) + Steps* st; + if( dc < Difficulty_Edit ) { - m_sInvalidReason = "steps not found"; - m_bInvalid = true; + st = SongUtil::GetStepsByDifficulty( pSong, pStyle->m_StepsType, dc ); } + else + { + st = SongUtil::GetStepsByDescription( pSong, pStyle->m_StepsType, sSteps ); + } + CHECK_INVALID_COND(m_pSteps, st, (st == NULL), + (ssprintf("Steps \"%s\" not found", sSteps.c_str()))); } } } else if( sName == "course" ) { - m_pCourse = SONGMAN->FindCourse( "", sValue ); - if( m_pCourse == NULL ) - { - m_sInvalidReason = ssprintf( "Course \"%s\" not found", sValue.c_str() ); - m_bInvalid = true; - } + CHECK_INVALID_COND(m_pCourse, SONGMAN->FindCourse("", sValue), + (SONGMAN->FindCourse("", sValue) == NULL), + (ssprintf( "Course \"%s\" not found", sValue.c_str()))); } else if( sName == "trail" ) @@ -307,20 +310,20 @@ void GameCommand::LoadOne( const Command& cmd ) const Style *pStyle = m_pStyle ? m_pStyle : GAMESTATE->GetCurrentStyle(); if( pCourse == NULL || pStyle == NULL ) { - LuaHelpers::ReportScriptError("Must set Course and Style to set Trail."); - m_sInvalidReason = "Course or Style not set"; - m_bInvalid = true; + MAKE_INVALID("Must set Course and Style to set Trail."); } else { const CourseDifficulty cd = StringToDifficulty( sTrail ); - ASSERT_M( cd != Difficulty_Invalid, ssprintf("Invalid difficulty '%s'", sTrail.c_str()) ); - - m_pTrail = pCourse->GetTrail( pStyle->m_StepsType, cd ); - if( m_pTrail == NULL ) + if(cd == Difficulty_Invalid) { - m_sInvalidReason = "trail not found"; - m_bInvalid = true; + MAKE_INVALID(ssprintf("Invalid difficulty '%s'", sTrail.c_str())); + } + else + { + Trail* tr = pCourse->GetTrail(pStyle->m_StepsType, cd); + CHECK_INVALID_COND(m_pTrail, tr, (tr == NULL), + ("Trail \"" + sTrail + "\" not found.")); } } } @@ -328,23 +331,28 @@ void GameCommand::LoadOne( const Command& cmd ) else if( sName == "setenv" ) { - if( cmd.m_vsArgs.size() == 3 ) - m_SetEnv[ cmd.m_vsArgs[1] ] = cmd.m_vsArgs[2]; + if((cmd.m_vsArgs.size() - 1) % 2 != 0) + { + MAKE_INVALID("Arguments to setenv game command must be key,value pairs."); + } + else + { + for(size_t i= 1; i < cmd.m_vsArgs.size(); i+= 2) + { + m_SetEnv[cmd.m_vsArgs[i]]= cmd.m_vsArgs[i+1]; + } + } } else if( sName == "songgroup" ) { - m_sSongGroup = sValue; + CHECK_INVALID_COND(m_sSongGroup, sValue, (!SONGMAN->DoesSongGroupExist(sValue)), ("Song group \"" + sValue + "\" does not exist.")); } else if( sName == "sort" ) { - m_SortOrder = StringToSortOrder( sValue ); - if( m_SortOrder == SortOrder_Invalid ) - { - m_sInvalidReason = ssprintf( "SortOrder \"%s\" is not valid.", sValue.c_str() ); - m_bInvalid = true; - } + SortOrder so= StringToSortOrder(sValue); + CHECK_INVALID_VALUE(m_SortOrder, so, SortOrder_Invalid, sortorder); } else if( sName == "weight" ) @@ -359,7 +367,8 @@ void GameCommand::LoadOne( const Command& cmd ) else if( sName == "goaltype" ) { - m_GoalType = StringToGoalType( sValue ); + GoalType go= StringToGoalType(sValue); + CHECK_INVALID_VALUE(m_GoalType, go, GoalType_Invalid, goaltype); } else if( sName == "profileid" ) @@ -412,15 +421,23 @@ void GameCommand::LoadOne( const Command& cmd ) else if( sName == "setpref" ) { - if( cmd.m_vsArgs.size() == 3 ) + if((cmd.m_vsArgs.size() - 1) % 2 != 0) { - IPreference *pPref = IPreference::GetPreferenceByName( cmd.m_vsArgs[1] ); - if( pPref == NULL ) + MAKE_INVALID("Arguments to setpref game command must be key,value pairs."); + } + else + { + for(size_t i= 1; i < cmd.m_vsArgs.size(); i+= 2) { - m_sInvalidReason = ssprintf("unknown preference \"%s\"", cmd.m_vsArgs[1].c_str() ); - m_bInvalid = true; + if(IPreference::GetPreferenceByName(cmd.m_vsArgs[i]) == NULL) + { + MAKE_INVALID("Unknown preference \"" + cmd.m_vsArgs[i] + "\"."); + } + else + { + m_SetPref[cmd.m_vsArgs[i]]= cmd.m_vsArgs[i+1]; + } } - pPref->FromString(cmd.m_vsArgs[2]); } } @@ -432,13 +449,19 @@ void GameCommand::LoadOne( const Command& cmd ) m_fMusicFadeOutVolume = static_cast(atof( cmd.m_vsArgs[1] )); m_fMusicFadeOutSeconds = static_cast(atof( cmd.m_vsArgs[2] )); } + else + { + MAKE_INVALID("Wrong number of args to fademusic."); + } } else { - RString sWarning = ssprintf( "Command '%s' is not valid.", cmd.GetOriginalCommandString().c_str() ); - LuaHelpers::ReportScriptError(sWarning, "INVALID_GAME_COMMAND"); + MAKE_INVALID(ssprintf( "Command '%s' is not valid.", cmd.GetOriginalCommandString().c_str())); } +#undef CHECK_INVALID_VALUE +#undef CHECK_INVALID_COND +#undef MAKE_INVALID } int GetNumCreditsPaid() @@ -457,8 +480,8 @@ int GetCreditsRequiredToPlayStyle( const Style *style ) { // GameState::GetCoinsNeededToJoin returns 0 if the coin mode isn't // CoinMode_Pay, which means the theme can't make sure that there are - // enough credits available. GameCommand::IsPlayable will cause a crash - // if there aren't enough credits. So we have to check the coin mode here + // enough credits available. + // So we have to check the coin mode here // and return 0 if the player doesn't have to pay. if( GAMESTATE->GetCoinMode() != CoinMode_Pay ) { @@ -692,7 +715,7 @@ void GameCommand::ApplySelf( const vector &vpns ) const } break; default: - FAIL_M(ssprintf("Invalid StyleType: %i", m_pStyle->m_StyleType)); + LuaHelpers::ReportScriptError("Invalid StyleType: " + m_pStyle->m_StyleType); } } if( m_dc != Difficulty_Invalid ) @@ -754,6 +777,14 @@ void GameCommand::ApplySelf( const vector &vpns ) const lua_pop( L, 1 ); LUA->Release(L); } + for(map::const_iterator setting= m_SetPref.begin(); setting != m_SetPref.end(); ++setting) + { + IPreference* pref= IPreference::GetPreferenceByName(setting->first); + if(pref != NULL) + { + pref->FromString(setting->second); + } + } if( !m_sSongGroup.empty() ) GAMESTATE->m_sPreferredSongGroup.Set( m_sSongGroup ); if( m_SortOrder != SortOrder_Invalid ) diff --git a/src/GameCommand.h b/src/GameCommand.h index d0641292ba..cad1ae9c62 100644 --- a/src/GameCommand.h +++ b/src/GameCommand.h @@ -34,7 +34,7 @@ public: m_sAnnouncer(""), m_sPreferredModifiers(""), m_sStageModifiers(""), m_sScreen(""), m_LuaFunction(), m_pSong(NULL), m_pSteps(NULL), m_pCourse(NULL), - m_pTrail(NULL), m_pCharacter(NULL), m_SetEnv(), + m_pTrail(NULL), m_pCharacter(NULL), m_SetEnv(), m_SetPref(), m_sSongGroup(""), m_SortOrder(SortOrder_Invalid), m_sSoundPath(""), m_vsScreensToPrepare(), m_iWeightPounds(-1), m_iGoalCalories(-1), m_GoalType(GoalType_Invalid), @@ -92,6 +92,7 @@ public: Trail* m_pTrail; Character* m_pCharacter; std::map m_SetEnv; + std::map m_SetPref; RString m_sSongGroup; SortOrder m_SortOrder; RString m_sSoundPath; // "" for no sound diff --git a/src/HoldJudgment.cpp b/src/HoldJudgment.cpp index eefa50632f..26656d8120 100644 --- a/src/HoldJudgment.cpp +++ b/src/HoldJudgment.cpp @@ -27,7 +27,7 @@ void HoldJudgment::Load( const RString &sPath ) void HoldJudgment::LoadFromNode( const XNode* pNode ) { RString sFile; - if( ActorUtil::GetAttrPath(pNode, "File", sFile) ) + if(!ActorUtil::GetAttrPath(pNode, "File", sFile)) { LuaHelpers::ReportScriptErrorFmt("%s: HoldJudgment: missing the attribute \"File\"", ActorUtil::GetWhere(pNode).c_str()); } diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index cefa228f94..9d2893e2b4 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -434,7 +434,7 @@ class OptionRowHandlerListSteps : public OptionRowHandlerList m_Def.m_vsChoices.push_back( "" ); m_aListEntries.push_back( GameCommand() ); } - else if( GAMESTATE->IsCourseMode() && GAMESTATE->m_pCurCourse ) // playing a course + else if(GAMESTATE->GetCurrentStyle() && 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 ); @@ -453,7 +453,7 @@ class OptionRowHandlerListSteps : public OptionRowHandlerList m_aListEntries.push_back( mc ); } } - else if( GAMESTATE->m_pCurSong ) // playing a song + else if(GAMESTATE->GetCurrentStyle() && GAMESTATE->m_pCurSong) // playing a song { m_Def.m_layoutType = StringToLayoutType( STEPS_ROW_LAYOUT_TYPE ); @@ -634,26 +634,31 @@ public: { unsigned i = iter - m_vSteps.begin(); vbSelOut[i] = true; - return; + continue; } // look for matching difficulty + bool matched= false; if( m_pDifficultyToFill ) { FOREACH_CONST( Difficulty, m_vDifficulties, d ) { unsigned i = d - m_vDifficulties.begin(); - if( *d == GAMESTATE->m_PreferredDifficulty[0] ) + if( *d == GAMESTATE->m_PreferredDifficulty[p] ) { vbSelOut[i] = true; + matched= true; vector v; v.push_back( p ); ExportOption( v, vbSelectedOut ); // current steps changed - continue; + break; } } } - // default to 1st - vbSelOut[0] = true; + if(!matched) + { + // default to 1st + vbSelOut[0] = true; + } } } virtual int ExportOption( const vector &vpns, const vector vbSelected[NUM_PLAYERS] ) const diff --git a/src/ScreenManager.cpp b/src/ScreenManager.cpp index 6df06f2a71..eacb2a207c 100644 --- a/src/ScreenManager.cpp +++ b/src/ScreenManager.cpp @@ -367,6 +367,12 @@ bool ScreenManager::AllowOperatorMenuButton() const return true; } +bool ScreenManager::IsScreenNameValid(RString const& name) const +{ + RString ClassName = THEME->GetMetric(name,"Class"); + return g_pmapRegistrees->find(ClassName) != g_pmapRegistrees->end(); +} + bool ScreenManager::IsStackedScreen( const Screen *pScreen ) const { // True if the screen is in the screen stack, but not the first. diff --git a/src/ScreenManager.h b/src/ScreenManager.h index 304cb77d5b..f43b000b66 100644 --- a/src/ScreenManager.h +++ b/src/ScreenManager.h @@ -39,6 +39,8 @@ public: Screen *GetScreen( int iPosition ); bool AllowOperatorMenuButton() const; + bool IsScreenNameValid(RString const& name) const; + // System messages void SystemMessage( const RString &sMessage ); void SystemMessageNoAnimate( const RString &sMessage ); From 273b76d3c7e626d1e43b5da37b800c383cfc3f4f Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Mon, 14 Jul 2014 18:02:28 -0600 Subject: [PATCH 08/12] ActorUtil::ResolvePath updated for error reporting. Changed BitmapText to work with either a font name or a path. ThemeManager::GetPathInfoRaw fixed to use correct error dialog. ThemeManager::GetPathInfo fixed to report correct error for missing theme elements. --- src/ActorUtil.cpp | 4 ++-- src/BitmapText.cpp | 28 +++++++++++++++------------- src/ThemeManager.cpp | 11 ++++++----- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/ActorUtil.cpp b/src/ActorUtil.cpp index a8a50dd550..3d48573c6c 100644 --- a/src/ActorUtil.cpp +++ b/src/ActorUtil.cpp @@ -55,7 +55,7 @@ bool ActorUtil::ResolvePath( RString &sPath, const RString &sName ) if( asPaths.empty() ) { RString sError = ssprintf( "%s: references a file \"%s\" which doesn't exist", sName.c_str(), sPath.c_str() ); - switch( Dialog::AbortRetryIgnore( sError, "BROKEN_FILE_REFERENCE" ) ) + switch(LuaHelpers::ReportScriptError(sError, "BROKEN_FILE_REFERENCE", true)) { case Dialog::abort: RageException::Throw( "%s", sError.c_str() ); @@ -76,7 +76,7 @@ bool ActorUtil::ResolvePath( RString &sPath, const RString &sName ) { RString sError = ssprintf( "%s: references a file \"%s\" which has multiple matches", sName.c_str(), sPath.c_str() ); sError += "\n" + join( "\n", asPaths ); - switch( Dialog::AbortRetryIgnore( sError, "BROKEN_FILE_REFERENCE" ) ) + switch(LuaHelpers::ReportScriptError(sError, "BROKEN_FILE_REFERENCE", true)) { case Dialog::abort: RageException::Throw( "%s", sError.c_str() ); diff --git a/src/BitmapText.cpp b/src/BitmapText.cpp index 3fc7525637..fd55c067d2 100644 --- a/src/BitmapText.cpp +++ b/src/BitmapText.cpp @@ -126,21 +126,23 @@ void BitmapText::LoadFromNode( const XNode* pNode ) ThemeManager::EvaluateString( sAltText ); RString sFont; - // The short circuiting loading condition that was here before wasn't short circuiting correctly on all platforms. - if(!ActorUtil::GetAttrPath(pNode, "Font", sFont)) + // Allow the Font attribute to be either a path or simply the name of a font. + // If it's a path, it will have a slash in it. + // Also allow it to be set through either "Font" or through "File". + if(!pNode->GetAttrValue("Font", sFont) && !pNode->GetAttrValue("File", sFont)) { - if(!ActorUtil::GetAttrPath(pNode, "File", sFont)) - { - if( !pNode->GetAttrValue("Font", sFont) && - !pNode->GetAttrValue("File", sFont) ) // accept "File" for backward compatibility - { - LuaHelpers::ReportScriptErrorFmt( "%s: BitmapText: Font or File attribute not found", - ActorUtil::GetWhere(pNode).c_str() ); - sFont = "Common Normal"; - } - } + LuaHelpers::ReportScriptErrorFmt("%s: BitmapText: Font or File attribute not found", + ActorUtil::GetWhere(pNode).c_str()); + sFont = "Common Normal"; + } + if(sFont.find("/") == RString::npos) + { + sFont= THEME->GetPathF("", sFont); + } + else if(!ActorUtil::ResolvePath(sFont, ActorUtil::GetWhere(pNode))) + { + sFont= THEME->GetPathF("", "Common Normal"); } - sFont = THEME->GetPathF( "", sFont ); LoadFromFont( sFont ); diff --git a/src/ThemeManager.cpp b/src/ThemeManager.cpp index b8adc806b5..fbf70293a6 100644 --- a/src/ThemeManager.cpp +++ b/src/ThemeManager.cpp @@ -722,7 +722,7 @@ bool ThemeManager::GetPathInfoToRaw( PathInfo &out, const RString &sThemeName_, "Verify that this redirect is correct.", sPath.c_str(), sNewFileName.c_str()); - switch( LuaHelpers::ReportScriptError(sMessage) ) + switch(LuaHelpers::ReportScriptError(sMessage, "", true)) { case Dialog::retry: ReloadMetrics(); @@ -815,11 +815,12 @@ try_element_again: goto try_element_again; case Dialog::ignore: { - RString error= ssprintf(sCategory + '/' + sFileName, - "could not be found in \"%s\" or \"%s\".", - GetThemeDirFromName(m_sCurThemeName).c_str(), - GetThemeDirFromName(SpecialFiles::BASE_THEME_NAME).c_str() ); + RString error= sCategory + '/' + sFileName + + " could not be found in \"" + + GetThemeDirFromName(m_sCurThemeName).c_str() + "\" or \"" + + GetThemeDirFromName(SpecialFiles::BASE_THEME_NAME).c_str() + "\"."; LOG->UserLog("Theme element", "%s", error.c_str()); + LOG->Warn(error.c_str()); LuaHelpers::ScriptErrorMessage(error); } From 0f72d7cc6b6a695c2777378f10404c65ebbc67bd Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 15 Jul 2014 13:28:06 -0600 Subject: [PATCH 09/12] Changed NoteSkinManager to use new error reporting and avoid crashing. Fixed DoChangeTheme to switch to the InitialScreen if the screen doesn't exist in the theme being changed to. Fixed error reporting in MeterDisplay. --- src/DifficultyList.cpp | 6 +++- src/GameLoop.cpp | 7 ++++ src/GameState.cpp | 2 +- src/MeterDisplay.cpp | 6 ++-- src/NoteSkinManager.cpp | 80 ++++++++++++++++++++++++++++------------- src/ScreenManager.cpp | 5 +++ 6 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/DifficultyList.cpp b/src/DifficultyList.cpp index d24d44ad5e..d24fadd9f4 100644 --- a/src/DifficultyList.cpp +++ b/src/DifficultyList.cpp @@ -37,7 +37,11 @@ void StepsDisplayList::LoadFromNode( const XNode* pNode ) { ActorFrame::LoadFromNode( pNode ); - ASSERT_M( !m_sName.empty(), "StepsDisplayList must have a Name" ); + if(m_sName.empty()) + { + LuaHelpers::ReportScriptError("StepsDisplayList must have a Name"); + return; + } ITEMS_SPACING_Y.Load( m_sName, "ItemsSpacingY" ); NUM_SHOWN_ITEMS.Load( m_sName, "NumShownItems" ); diff --git a/src/GameLoop.cpp b/src/GameLoop.cpp index d30cba98da..74ccdb0d6a 100644 --- a/src/GameLoop.cpp +++ b/src/GameLoop.cpp @@ -145,6 +145,13 @@ namespace StepMania::ResetGame(); SCREENMAN->ThemeChanged(); + // Not all themes use the same screen names! Check whether the new + // screen is valid in the new theme before setting it. Use the + // InitialScreen metric if it's not. + if(!SCREENMAN->IsScreenNameValid(g_sNewScreen)) + { + g_sNewScreen= THEME->GetMetric("Common", "InitialScreen"); + } SCREENMAN->SetNewScreen( g_sNewScreen ); g_sNewTheme = RString(); diff --git a/src/GameState.cpp b/src/GameState.cpp index 0436e65a43..67a3135d87 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -223,7 +223,7 @@ void GameState::ApplyGameCommand( const RString &sCommand, PlayerNumber pn ) RString sWhy; if( !m.IsPlayable(&sWhy) ) { - LuaHelpers::ReportScriptErrorFmt("Can't apply mode \"%s\": %s", sCommand.c_str(), sWhy.c_str()); + LuaHelpers::ReportScriptErrorFmt("Can't apply GameCommand \"%s\": %s", sCommand.c_str(), sWhy.c_str()); return; } diff --git a/src/MeterDisplay.cpp b/src/MeterDisplay.cpp index 389b5311ce..9fa6e19c4c 100644 --- a/src/MeterDisplay.cpp +++ b/src/MeterDisplay.cpp @@ -35,11 +35,9 @@ void MeterDisplay::LoadFromNode( const XNode* pNode ) if( pStream == NULL ) { LuaHelpers::ReportScriptErrorFmt("%s: MeterDisplay: missing the \"Stream\" attribute", ActorUtil::GetWhere(pNode).c_str()); + return; } - else - { - m_sprStream.LoadActorFromNode( pStream, this ); - } + m_sprStream.LoadActorFromNode( pStream, this ); m_sprStream->SetName( "Stream" ); //LOAD_ALL_COMMANDS( m_sprStream ); this->AddChild( m_sprStream ); diff --git a/src/NoteSkinManager.cpp b/src/NoteSkinManager.cpp index fa4b5415bc..0ca23377a6 100644 --- a/src/NoteSkinManager.cpp +++ b/src/NoteSkinManager.cpp @@ -112,7 +112,11 @@ void NoteSkinManager::LoadNoteSkinDataRecursive( const RString &sNoteSkinName_, while(1) { ++iDepth; - ASSERT_M( iDepth < 20, "Circular NoteSkin fallback references detected." ); + if(iDepth >= 20) + { + LuaHelpers::ReportScriptError("Circular NoteSkin fallback references detected.", "NOTESKIN_ERROR"); + return; + } RString sDir = SpecialFiles::NOTESKINS_DIR + m_pCurGame->m_szName + "/" + sNoteSkinName + "/"; if( !FILEMAN->IsADirectory(sDir) ) @@ -120,8 +124,9 @@ void NoteSkinManager::LoadNoteSkinDataRecursive( const RString &sNoteSkinName_, sDir = GLOBAL_BASE_DIR + sNoteSkinName + "/"; if( !FILEMAN->IsADirectory(sDir) ) { - LOG->Trace( "NoteSkin \"%s\" references skin \"%s\" that is not present", - data_out.sName.c_str(), sNoteSkinName.c_str() ); + LuaHelpers::ReportScriptError("NoteSkin \"" + data_out.sName + + "\" references skin \"" + sNoteSkinName + "\" that is not present", + "NOTESKIN_ERROR"); return; } } @@ -234,7 +239,11 @@ void NoteSkinManager::GetAllNoteSkinNamesForGame( const Game *pGame, vector::const_iterator it = g_mapNameToData.find(sNoteSkinName); @@ -245,8 +254,12 @@ RString NoteSkinManager::GetMetric( const RString &sButtonName, const RString &s if( data.metrics.GetValue( sButtonName, sValue, sReturn ) ) return sReturn; if( !data.metrics.GetValue( "NoteDisplay", sValue, sReturn ) ) - RageException::Throw( "Could not read metric \"%s::%s\" or \"NoteDisplay::%s\" in \"%s\".", - sButtonName.c_str(), sValue.c_str(), sValue.c_str(), sNoteSkinName.c_str() ); + { + LuaHelpers::ReportScriptError("Could not read metric \"" + sButtonName + + "::" + sValue + "\" or \"NoteDisplay::" + sValue + "\" in \"" + + sNoteSkinName + "\".", "NOTESKIN_ERROR"); + return ""; + } return sReturn; } @@ -320,15 +333,18 @@ RString NoteSkinManager::GetPath( const RString &sButtonName, const RString &sEl sButtonName.c_str(), sElement.c_str(), sPaths.c_str() ); - if( Dialog::AbortRetryIgnore(message) == Dialog::retry ) + switch(LuaHelpers::ReportScriptError(message, "NOTESKIN_ERROR", true)) { - FOREACH_CONST( RString, data.vsDirSearchOrder, dir ) - FILEMAN->FlushDirCache( *dir ); - g_PathCache.clear(); - return GetPath( sButtonName, sElement ); + case Dialog::retry: + FOREACH_CONST(RString, data.vsDirSearchOrder, dir) + FILEMAN->FlushDirCache(*dir); + g_PathCache.clear(); + return GetPath(sButtonName, sElement); + case Dialog::abort: + RageException::Throw("%s", message.c_str()); + case Dialog::ignore: + return ""; } - - RageException::Throw( "%s", message.c_str() ); } int iLevel = 0; @@ -355,15 +371,18 @@ RString NoteSkinManager::GetPath( const RString &sButtonName, const RString &sEl "Verify that this redirect is correct.", sPath.c_str(), sNewFileName.c_str()); - if( Dialog::AbortRetryIgnore(message) == Dialog::retry ) + switch(LuaHelpers::ReportScriptError(message, "NOTESKIN_ERROR", true)) { - FOREACH_CONST( RString, data.vsDirSearchOrder, dir ) - FILEMAN->FlushDirCache( *dir ); - g_PathCache.clear(); - return GetPath( sButtonName, sElement ); + case Dialog::retry: + FOREACH_CONST(RString, data.vsDirSearchOrder, dir) + FILEMAN->FlushDirCache(*dir); + g_PathCache.clear(); + return GetPath(sButtonName, sElement); + case Dialog::abort: + RageException::Throw("%s", message.c_str()); + case Dialog::ignore: + return ""; } - - RageException::Throw( "%s", message.c_str() ); } sPath = sRealPath; @@ -376,7 +395,11 @@ RString NoteSkinManager::GetPath( const RString &sButtonName, const RString &sEl bool NoteSkinManager::PushActorTemplate( Lua *L, const RString &sButton, const RString &sElement, bool bSpriteOnly ) { map::const_iterator iter = g_mapNameToData.find( m_sCurrentNoteSkin ); - ASSERT( iter != g_mapNameToData.end() ); + if(iter == g_mapNameToData.end()) + { + LuaHelpers::ReportScriptError("No current noteskin set!", "NOTESKIN_ERROR"); + return false; + } const NoteSkinData &data = iter->second; LuaThreadVariable varPlayer( "Player", LuaReference::Create(m_PlayerNumber) ); @@ -385,7 +408,11 @@ bool NoteSkinManager::PushActorTemplate( Lua *L, const RString &sButton, const R LuaThreadVariable varElement( "Element", sElement ); LuaThreadVariable varSpriteOnly( "SpriteOnly", LuaReference::Create(bSpriteOnly) ); - ASSERT( !data.m_Loader.IsNil() ); + if(data.m_Loader.IsNil()) + { + LuaHelpers::ReportScriptError("No loader for noteskin!", "NOTESKIN_ERROR"); + return false; + } data.m_Loader.PushSelf( L ); lua_remove( L, -2 ); lua_getfield( L, -1, "Load" ); @@ -437,7 +464,7 @@ RString NoteSkinManager::GetPathFromDirAndFile( const RString &sDir, const RStri if( matches.size() > 1 ) { RString sError = "Multiple files match '"+sDir+sFileName+"'. Please remove all but one of these files."; - Dialog::OK( sError ); + LuaHelpers::ReportScriptError(sError, "NOTESKIN_ERROR"); } return matches[0]; @@ -469,7 +496,12 @@ public: static int x ## ForNoteSkin( T* p, lua_State *L ) \ { \ const RString sOldNoteSkin = p->GetCurrentNoteSkin(); \ - p->SetCurrentNoteSkin( SArg(n+1) ); \ + RString nsname= SArg(n+1); \ + if(!p->DoesNoteSkinExist(nsname)) \ + { \ + luaL_error(L, "Noteskin \"%s\" does not exist.", nsname.c_str()); \ + } \ + p->SetCurrentNoteSkin( nsname ); \ x( p, L ); \ p->SetCurrentNoteSkin( sOldNoteSkin ); \ return 1; \ diff --git a/src/ScreenManager.cpp b/src/ScreenManager.cpp index eacb2a207c..b8b75aa85d 100644 --- a/src/ScreenManager.cpp +++ b/src/ScreenManager.cpp @@ -722,6 +722,11 @@ void ScreenManager::LoadDelayedScreen() { RString sScreenName = m_sDelayedScreen; m_sDelayedScreen = ""; + if(!IsScreenNameValid(sScreenName)) + { + LuaHelpers::ReportScriptError("Tried to go to invalid screen: " + sScreenName, "INVALID_SCREEN"); + return; + } // Pop the top screen, if any. ScreenMessage SM = PopTopScreenInternal(); From 8a65dfaefa6c5e159ce5491be82bc3454f398d3f Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Wed, 16 Jul 2014 02:03:06 -0600 Subject: [PATCH 10/12] Tested and fixed error reporting for NoteSkinManager. Changed Sprite::Sprite() to load the default texture. --- src/NoteSkinManager.cpp | 34 +++++++++++++++++++++++++++++----- src/Sprite.cpp | 3 +++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/NoteSkinManager.cpp b/src/NoteSkinManager.cpp index 0ca23377a6..198c9812db 100644 --- a/src/NoteSkinManager.cpp +++ b/src/NoteSkinManager.cpp @@ -291,7 +291,14 @@ RString NoteSkinManager::GetPath( const RString &sButtonName, const RString &sEl if( it != g_PathCache.end() ) return it->second; - map::const_iterator iter = g_mapNameToData.find( m_sCurrentNoteSkin ); + if(m_sCurrentNoteSkin.empty()) + { + LuaHelpers::ReportScriptError("NOTESKIN:GetPath: No noteskin currently set.", "NOTESKIN_ERROR"); + return ""; + } + RString sNoteSkinName = m_sCurrentNoteSkin; + sNoteSkinName.MakeLower(); + map::const_iterator iter = g_mapNameToData.find( sNoteSkinName ); ASSERT( iter != g_mapNameToData.end() ); const NoteSkinData &data = iter->second; @@ -351,8 +358,12 @@ RString NoteSkinManager::GetPath( const RString &sButtonName, const RString &sEl while( GetExtension(sPath) == "redir" ) { iLevel++; - ASSERT_M( iLevel < 100, ssprintf("Infinite recursion while looking up %s - %s", sButtonName.c_str(), sElement.c_str()) ); - + if(iLevel >= 100) + { + LuaHelpers::ReportScriptError("Infinite recursion while looking up " + + sButtonName + " - " + sElement, "NOTESKIN_ERROR"); + return ""; + } RString sNewFileName; GetFileContents( sPath, sNewFileName, true ); RString sRealPath; @@ -434,6 +445,10 @@ Actor *NoteSkinManager::LoadActor( const RString &sButton, const RString &sEleme if( pNode.get() == NULL ) { // XNode will warn about the error + if(bSpriteOnly) + { + return new Sprite; + } return new Actor; } @@ -446,7 +461,11 @@ Actor *NoteSkinManager::LoadActor( const RString &sButton, const RString &sEleme // Make sure pActor is a Sprite (or something derived from Sprite). Sprite *pSprite = dynamic_cast( pRet ); if( pSprite == NULL ) - LuaHelpers::ReportScriptErrorFmt( "%s: %s %s must be a Sprite", m_sCurrentNoteSkin.c_str(), sButton.c_str(), sElement.c_str() ); + { + LuaHelpers::ReportScriptErrorFmt("%s: %s %s must be a Sprite", m_sCurrentNoteSkin.c_str(), sButton.c_str(), sElement.c_str()); + delete pRet; + return new Sprite; + } } return pRet; @@ -463,7 +482,12 @@ RString NoteSkinManager::GetPathFromDirAndFile( const RString &sDir, const RStri if( matches.size() > 1 ) { - RString sError = "Multiple files match '"+sDir+sFileName+"'. Please remove all but one of these files."; + RString sError = "Multiple files match '"+sDir+sFileName+"'. Please remove all but one of these files: "; + sError+= matches[1]; + for(size_t n= 1; n < matches.size(); ++n) + { + sError+= ", " + matches[n]; + } LuaHelpers::ReportScriptError(sError, "NOTESKIN_ERROR"); } diff --git a/src/Sprite.cpp b/src/Sprite.cpp index 0e67cde16e..3a82cf343b 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -32,6 +32,9 @@ Sprite::Sprite() m_fTexCoordVelocityX = 0; m_fTexCoordVelocityY = 0; + + // An uninitialized sprite should be valid to display. -Kyz + Load(TEXTUREMAN->GetDefaultTextureID()); } From a2e6d186e306c7f785f4e7fe99d52e164c10a9e0 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Fri, 18 Jul 2014 16:29:46 -0600 Subject: [PATCH 11/12] Created Def.LogDisplay for displaying messages in a log. Changed error reporting layer to use Def.LogDisplay. Changed engine error reporting to not clip errors to a single line. --- Docs/Luadoc/Lua.xml | 1 + Docs/Luadoc/LuaDocumentation.xml | 4 + Docs/Themerdocs/actordef.txt | 3 +- .../BGAnimations/ScreenSystemLayer error.lua | 175 +--------- Themes/_fallback/Scripts/02 Utilities.lua | 72 ++++ Themes/_fallback/Scripts/04 LogDisplay.lua | 313 ++++++++++++++++++ src/LuaManager.cpp | 31 +- src/ScreenDebugOverlay.cpp | 4 +- 8 files changed, 418 insertions(+), 185 deletions(-) create mode 100644 Themes/_fallback/Scripts/04 LogDisplay.lua diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index c2297a38d2..bc4e99d244 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1822,6 +1822,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index bee1948b6f..61886525d3 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -749,6 +749,10 @@ save yourself some time, copy this for undocumented things: Tries to read the file at sPath. If successful, it returns the file's contents. If unsuccessful, it returns two values: nil and "error". + + Reports the error through the error reporting system. error_type is the type used for the dialog that is presented, a dialog will not appear for a type the user has chosen to ignore.
+ error is optional an defaults to "Script error occurred.". error_type is optional and defaults to "LUA_ERROR". +
Calls func(...) with two LuaThreadVariables set, and returns the return values of func(). diff --git a/Docs/Themerdocs/actordef.txt b/Docs/Themerdocs/actordef.txt index 3257511314..551600a5cd 100644 --- a/Docs/Themerdocs/actordef.txt +++ b/Docs/Themerdocs/actordef.txt @@ -26,6 +26,7 @@ GrooveRadar HelpDisplay HoldJudgment InputList +LogDisplay MemoryCardDisplay MeterDisplay Model @@ -43,4 +44,4 @@ Sprite StepsDisplay StepsDisplayList TextBanner -WorkoutGraph \ No newline at end of file +WorkoutGraph diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua index fed9f3808b..830360a71f 100644 --- a/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua @@ -1,56 +1,9 @@ -- If you are a common themer, DO NOT INCLUDE THIS FILE IN YOUR THEME. -- This layer is purely for error reporting, so that you can see errors on screen easily while running stepmania. -- If you include this file in your theme, you will not benefit from any improvements in error reporting. --- If you want a different background behind the errors, then include "ScreenSystemLayer errorbg.lua" in your theme. -- If you want to adjust how long errors stay on screen for, call the "SetErrorMessageTime" function. (see usage notes in comments above that function) --- Minor text formatting functions from Kyzentun. --- TODO: Figure out why BitmapText:maxwidth doesn't do what I want. -local function width_limit_text(text, limit, natural_zoom) - natural_zoom= natural_zoom or 1 - if text:GetWidth() * natural_zoom > limit then - text:zoomx(limit / text:GetWidth()) - else - text:zoomx(natural_zoom) - end -end - -local function width_clip_text(text, limit) - local full_text= text:GetText() - local fits= text:GetZoomedWidth() <= limit - local prev_max= #full_text - 1 - local prev_min= 0 - if not fits then - while prev_max - prev_min > 1 do - local new_max= math.round((prev_max + prev_min) / 2) - text:settext(full_text:sub(1, 1+new_max)) - if text:GetZoomedWidth() <= limit then - prev_min= new_max - else - prev_max= new_max - end - end - text:settext(full_text:sub(1, 1+prev_min)) - end -end - -local function width_clip_limit_text(text, limit, natural_zoom) - natural_zoom= natural_zoom or text:GetZoomY() - local text_width= text:GetWidth() * natural_zoom - if text_width > limit * 2 then - text:zoomx(natural_zoom * .5) - width_clip_text(text, limit) - else - width_limit_text(text, limit, natural_zoom) - end -end - -local text_actors= {} local line_height= 12 -- A good line height for Common Normal at .5 zoom. -local line_width= SCREEN_WIDTH - 20 - -local next_message_actor= 1 -local show_requested= false local min_message_time= {show= 1, hide= .03125} local default_message_time= {show= 4, hide= .125} @@ -92,130 +45,12 @@ function GetErrorMessageTimeDefault(which) return default_message_time[which] end -local frame_args= { - Name="Error frame", - InitCommand= function(self) - self:y(-SCREEN_HEIGHT) - end, - ScriptErrorMessageCommand = function(self, params) - show_requested= false - self:stoptweening() - self:visible(true) - local covered_height= math.min(line_height * next_message_actor, SCREEN_HEIGHT) - self:y(-SCREEN_HEIGHT + covered_height) - self:sleep(message_time.show) - self:queuecommand("DecNextActor") - -- Shift the text on all the actors being shown up by one. - for i= next_message_actor, 1, -1 do - if text_actors[i] then - text_actors[i]:visible(true) - if i > 1 and text_actors[i-1] then - text_actors[i]:settext(text_actors[i-1]:GetText()) - else - -- Someone long ago decided that it was a good idea for "::" to be - -- a synonym for "\n", so that strings in metrics could have line - -- breaks. - -- So replace "::" with ":" so we don't have unwanted line breaks. - text_actors[i]:settext(params.Message:gsub("::", ":")) - end - width_clip_limit_text(text_actors[i], line_width) - end - end - if next_message_actor <= #text_actors then - next_message_actor= next_message_actor + 1 - end - end, - ToggleErrorsMessageCommand= function(self, params) - if show_requested then - self:playcommand("HideErrors") - else - self:playcommand("ShowErrors") - end - end, - ShowErrorsMessageCommand= function(self, params) - show_requested= true - for i, mactor in ipairs(text_actors) do - if mactor:GetText() ~= "" then - mactor:visible(true) - next_message_actor= next_message_actor + 1 - end - end - next_message_actor= next_message_actor - 1 - local covered_height= math.min(line_height * next_message_actor, SCREEN_HEIGHT) - self:stoptweening() - self:visible(true) - self:linear(message_time.hide) - self:y(-SCREEN_HEIGHT + covered_height) - end, - HideErrorsMessageCommand= function(self, params) - if not show_requested then return end - show_requested= false - self:stoptweening() - next_message_actor= 1 - self:linear(message_time.hide) - self:y(-SCREEN_HEIGHT) - self:queuecommand("HideText") - end, - ClearErrorsMessageCommand= function(self, params) - -- This is so that someone using ShowErrorsMessageCommand can clear ones - -- they've dealt with. - -- Only allow clearing errors that we have scrolled off screen. - for i, mactor in ipairs(text_actors) do - if not mactor:GetVisible() then - mactor:settext("") - end - end - end, - DecNextActorCommand= function(self) - self:linear(message_time.hide) - self:y(self:GetY()-line_height) - if text_actors[next_message_actor] then - text_actors[next_message_actor]:visible(false) - end - next_message_actor= next_message_actor - 1 - if next_message_actor > 1 then - self:queuecommand("DecNextActor") - else - self:queuecommand("Off") - end - end, - HideTextCommand= function(self) - for i, mactor in ipairs(text_actors) do - mactor:visible(false) - end - end, - OffCommand= cmd(visible,false), - Def.Quad { - Name= "errorbg", - InitCommand= function(self) - self:setsize(SCREEN_WIDTH, SCREEN_HEIGHT) - self:horizalign(left) - self:vertalign(top) - self:diffuse(color("0,0,0,0")) - self:diffusealpha(.85) - end, - } +local log_args= { + Name= "ScriptError", + ReplaceLinesWhenHidden= true, + Times= message_time, } --- Create enough text actors that we can fill the screen. -local num_text= SCREEN_HEIGHT / line_height -for i= 1, num_text do - frame_args[#frame_args+1]= LoadFont("Common","Normal") .. { - Name="Text" .. i, - InitCommand= function(self) - -- Put them in the list in reverse order so the ones at the bottom of the screen are used first. - text_actors[num_text-i+1]= self - self:horizalign(left) - self:vertalign(top) - self:x(SCREEN_LEFT + 10) - self:y(SCREEN_TOP + (line_height * (i-1)) + 2) - self:shadowlength(1) - self:zoom(.5) - self:visible(false) - end, - OffCommand= cmd(visible,false), - } -end Trace("Loaded error layer.") -return Def.ActorFrame(frame_args) +return Def.LogDisplay(log_args) diff --git a/Themes/_fallback/Scripts/02 Utilities.lua b/Themes/_fallback/Scripts/02 Utilities.lua index aa99ec0767..2d456e770b 100644 --- a/Themes/_fallback/Scripts/02 Utilities.lua +++ b/Themes/_fallback/Scripts/02 Utilities.lua @@ -371,6 +371,78 @@ function IsUsingWideScreen() end; end; +-- Minor text formatting functions from Kyzentun. +-- TODO: Figure out why BitmapText:maxwidth doesn't do what I want. +-- Intentionally undocumented because they should be moved to BitmapText ASAP +function width_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or 1 + if text:GetWidth() * natural_zoom > limit then + text:zoomx(limit / text:GetWidth()) + else + text:zoomx(natural_zoom) + end +end + +function width_clip_text(text, limit) + local full_text= text:GetText() + local fits= text:GetZoomedWidth() <= limit + local prev_max= #full_text - 1 + local prev_min= 0 + if not fits then + while prev_max - prev_min > 1 do + local new_max= math.round((prev_max + prev_min) / 2) + text:settext(full_text:sub(1, 1+new_max)) + if text:GetZoomedWidth() <= limit then + prev_min= new_max + else + prev_max= new_max + end + end + text:settext(full_text:sub(1, 1+prev_min)) + end +end + +function width_clip_limit_text(text, limit, natural_zoom) + natural_zoom= natural_zoom or text:GetZoomY() + local text_width= text:GetWidth() * natural_zoom + if text_width > limit * 2 then + text:zoomx(natural_zoom * .5) + width_clip_text(text, limit) + else + width_limit_text(text, limit, natural_zoom) + end +end + +function convert_text_to_indented_lines(text, indent, width, text_zoom) + local text_as_lines= split("\n", text:GetText()) + local indented_lines= {} + for i, line in ipairs(text_as_lines) do + local remain= line + local sub_lines= 0 + repeat + text:settext(remain) + local clipped= false + local indent_mult= 0 + if i > 1 then + indent_mult= indent_mult + 1 + end + if sub_lines > 0 then + indent_mult= 2 + end + local usable_width= width - (indent * indent_mult) + if text:GetWidth() * text_zoom > usable_width * 2 then + clipped= true + width_clip_text(text, usable_width * 2) + end + indented_lines[#indented_lines+1]= { + indent_mult, text:GetText()} + remain= remain:sub(#text:GetText()+1) + sub_lines= sub_lines + 1 + until not clipped + end + return indented_lines +end + -- (c) 2005-2011 Glenn Maynard, Chris Danford, SSC -- All rights reserved. -- diff --git a/Themes/_fallback/Scripts/04 LogDisplay.lua b/Themes/_fallback/Scripts/04 LogDisplay.lua new file mode 100644 index 0000000000..4bfb50564b --- /dev/null +++ b/Themes/_fallback/Scripts/04 LogDisplay.lua @@ -0,0 +1,313 @@ +-- This is a little fake actor class meant for displaying lines of a log. +-- It's placed inside Def, but it's actually just an ActorFrame with some +-- children and special commands. + +-- LogDisplay listens for several messages that control its behavior. +-- The name given to the LogDisplay is added to the name of the message to +-- make the messages unique to this LogDisplay. So to issue a command to +-- the LogDisplay, broadcast the message "". +-- For a LogDisplay named "Foo", you would broadcase "Foo" to add to the +-- log, "ToggleFoo" to toggle whether it's shown or hidden, and so on. + +-- Some of the control messages take tables of args. +-- Arg elements of the form "name= type" are named, and must be a value of +-- the given type. +-- Arg elements of the form "name" are unnamed. + +-- The messages are: +-- : params= {message= string, dont_show= bool} +-- Adds message to the log to be shown when revealing. +-- If dont_show is false, then Show will be automatically executed with +-- auto_hide= true. +-- Messages with line breaks will take up multiple lines. +-- The newest message in the log has index 1. +-- Toggle: No params +-- Executes either Show or Hide, depending on the current state. +-- Show: params= {range= {last_message, lines}, auto_hide= bool} +-- Shows the messages currently in the log. +-- range[1] is the index of the last message to show. +-- range[2] is the max number of lines to show. +-- range[1] defaults to a cleavage shot. +-- range[2] defaults to MaxDisplayLines. +-- If auto_hide is non-nil, Hide will be execuated after a short time. +-- If auto_hide is a number, it will be used as the time to wait before +-- hiding. Otherwise, the Times.show will be used. +-- Query: No params +-- Broadcasts "Response" message with a table containing: +-- {log_len= number, hidden= bool} +-- log_len is the number of messages currently in the log. +-- hidden is whether the LogDisplay is currently hidden. +-- Hide: No params +-- Hides the LogDisplay. +-- Clear: params= {messages} +-- Clears the messages in the log. If messages is a number, only clears +-- that many messages, oldest first. + +-- params to Def.LogDisplay(): +-- { +-- Name= string +-- The name will be used as the name of the main ActorFrame and to +-- control what messages the LogDisplay listens for. +-- MaxLogLen= number +-- The maximum number of messages to store in the log. +-- MaxDisplayLines= number +-- The maximum number of lines that can be displayed at a time. +-- ReplaceLinesWhenHidden= bool +-- If this is true, any messages recieved while the LogDisplay is hidden +-- will replace the message currently at the front of the log. +-- If this is false, messages recieved while the LogDisplay is hidden +-- will push other messages back. Messages pushed past MaxLines will be +-- removed. +-- Font= font name +-- The name of the font to use. This will be passed to THEME:GetPathF, +-- so it should not be a path. +-- LineHeight= number +-- The height in pixels to use between lines. +-- LineWidth= number +-- The width that lines should be limited to. +-- TextZoom= number +-- The zoom factor to apply to the text. +-- TextColor= color +-- The color to use for the text. +-- Times= {show= number, hide= number} +-- show is the amount of time to wait before automatically hiding when +-- Show is executed with AutoHide == true. It's passed as a table, so +-- if you keep a copy of the table, you should be able to modify it to +-- change the time. +-- hide is used as the time for the default hide command to hide. +-- Indent= number +-- The amount in pixels to indent lines that were part of a multi-line +-- message. +-- Hide= function(self) +-- This command will be executed when the LogDisplay needs to be hidden. +-- Hide will also be executed during the InitCommand, so the +-- LogDisplay will start in the hidden state. +-- Show= function(self, Lines) +-- This command will be executed when the LogDisplay needs to be shown. +-- Lines is the number of lines to show. +-- } +-- Any actors placed in params will be used and drawn behind the text. +-- If there are no actors in params, a quad filling the area will be used. +-- Reasonable defaults are provided for everything except Name. If Name is +-- blank, you get nothing. Defaults assume the LogDisplay should fill the +-- screen when in use. + +-- Below is the implementation of the above features. + +local log_display_mt= { + __index= { + create_actors= function(self, params) + self.name= params.Name + self.font= params.Font or "Common Normal" + self.line_height= params.LineHeight or 12 + self.line_width= params.LineWidth or SCREEN_WIDTH + self.text_zoom= params.TextZoom or .5 + self.text_color= params.TextColor or color("#93a1a1") + self.max_lines= params.MaxLines or SCREEN_HEIGHT / self.line_height + self.max_log= params.MaxLogLen or self.max_lines + self.indent= params.Indent or 8 + self.times= params.Times + self.param_hide= params.Hide or + function(subself) + subself:linear(params.Times.hide) + subself:y(-SCREEN_HEIGHT) + end + self.param_show= params.Show or + function(subself, lines) + subself:visible(true) + subself:linear(params.Times.hide) + local cover= math.min(self.line_height * (lines+.5), SCREEN_HEIGHT) + subself:y(-SCREEN_HEIGHT + cover) + end + + self.text_actors= {} + self.message_log= {} + self.messes_since_update= 0 + + local name_mess= self.name .. "MessageCommand" + local args= { + Name= self.name, + InitCommand= function(subself) + self.container= subself; -- This semicolon ends this statement. + -- Without it, the next would be ambiguous syntax. + -- Let the InitCommand passed in params do something. + (params.InitCommand or function() end)(subself) + self:hide() + end, + OffCommand= function(subself) + subself:visible(false) + for i, actor in ipairs(self.text_actors) do + actor:visible(false) + end + end, + [name_mess]= function(subself, mess) + if not mess.message then return end + if self.messes_since_update > self.max_log then return end + -- Long ago, someone decided that "::" should be an alias for "\n" + -- and hardcoded it into BitmapText. + local message= tostring(mess.message):gsub("::", ":") + if params.ReplaceLinesWhenHidden and self.hidden then + self:clear() + self.message_log[1]= message + else + table.insert(self.message_log, 1, message) + if #self.message_log > self.max_log then + table.remove(self.message_log) + end + end + if not mess.dont_show or not self.hidden then + self.messes_since_update= self.messes_since_update + 1 + self.hidden= false + subself:stoptweening() + subself:queuecommand("Update") + end + end, + ["Toggle" .. name_mess]= function(subself) + if self.hidden then + self:show() + else + self:hide() + end + end, + ["Hide" .. name_mess]= function(subself) + self:hide() + end, + ["Show" .. name_mess]= function(subself, mess) + self:show(mess.range, mess.auto_hide) + end, + ["Clear" .. name_mess]= function(subself, mess) + self:clear(mess[1]) + end, + UpdateCommand= function(subself) + self:show(nil, true) + self.messes_since_update= 0 + end, + LoadFont(self.font) .. + { + Name="WidthTester", + InitCommand= function(subself) + self.width_tester= subself + subself:zoom(self.text_zoom) + subself:visible(false) + end + } + } + if #params == 0 then + args[#args+1]= Def.Quad { + Name= "Logbg", + InitCommand= function(subself) + subself:setsize(self.line_width, self.line_height*self.max_lines) + subself:horizalign(left) + subself:vertalign(top) + subself:diffuse(color("#002b36")) + subself:diffusealpha(.85) + end, + } + else + -- Add bg actors passed through params. + for i, actor in ipairs(params) do + args[#args+1]= actor + end + end + -- Add commands passed through params. + for name, command in pairs(params) do + if type(name) == "string" and name:find("Command") and not args[name] then + args[name]= command + end + end + for i= 1, self.max_lines do + args[#args+1]= LoadFont(self.font) .. + { + Name="Text" .. i, + InitCommand= function(subself) + -- Put them in the list in reverse order so the ones at the + -- bottom of the screen are used first. + self.text_actors[self.max_lines-i+1]= subself + subself:horizalign(left) + subself:vertalign(top) + subself:y(self.line_height * (i-1)) + subself:zoom(self.text_zoom) + subself:diffuse(self.text_color) + subself:visible(false) + end, + OffCommand= cmd(visible,false), + } + end + return Def.ActorFrame(args) + end, + hide= function(self) + self.container:stoptweening() + self.param_hide(self.container) + self.container:queuecommand("Off") + self.hidden= true + end, + show= function(self, range, auto_hide) + if not range then range= {} end + local start= range[1] or 1 + local lmax= range[2] or self.max_lines + local indented_lines= {} + local next_message= start + local num_lines= 0 + while num_lines < lmax and self.message_log[next_message] do + self.width_tester:settext(self.message_log[next_message]) + local lines_to_add= convert_text_to_indented_lines( + self.width_tester, self.indent, self.line_width, self.text_zoom) + indented_lines[#indented_lines+1]= lines_to_add + num_lines= num_lines + #lines_to_add + next_message= next_message + 1 + end + local use_next= 1 + local used= 0 + for i, mess in ipairs(indented_lines) do + for l, line in ipairs(mess) do + -- Start at the end of the mess because the text actors are in + -- reverse order. + local ind= use_next + (#mess - l) + local actor= self.text_actors[ind] + if actor and used <= lmax then + actor:settext(line[2]) + local indent= 8 + self.indent * line[1] + local lw= self.line_width - (indent + 8) + width_limit_text(actor, lw, self.text_zoom) + actor:x(indent) + actor:visible(true) + used= used + 1 + end + end + use_next= use_next + #mess + end + self.container:stoptweening() + self.param_show(self.container, used) + self.hidden= false + if auto_hide then + self.container:sleep(self.times.show) + self.container:queuecommand("Hide" .. self.name) + end + end, + clear= function(self, messes) + if #self.message_log < 1 then return end + if not messes then + self.message_log= {} + else + for i= 1, messes do + table.remove(self.message_log) + if #self.message_log < 1 then break end + end + end + end +}} + +function Def.LogDisplay(params) + if type(params.Name) ~= "string" or params.Name == "" then + ReportScriptError("Cannot create a LogDisplay without a name.") + return nil + end + + if not params.Times then params.Times= {show= 4, hide= .125} end + if not params.Times.show then params.Times.show= 4 end + if not params.Times.hide then params.Times.hide= .125 end + + local new_log_display= setmetatable({}, log_display_mt) + _G[params.Name .. "LogDisplay"]= new_log_display + return new_log_display:create_actors(params) +end diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index e76a7d8321..39eb6b452d 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -794,27 +794,17 @@ bool LuaHelpers::LoadScript( Lua *L, const RString &sScript, const RString &sNam void LuaHelpers::ScriptErrorMessage(RString const& Error) { Message msg("ScriptError"); - msg.SetParam("Message", Error); + msg.SetParam("message", Error); MESSAGEMAN->Broadcast(msg); } Dialog::Result LuaHelpers::ReportScriptError(RString const& Error, RString ErrorType, bool UseAbort) { - size_t line_break_pos= Error.find('\n'); - RString short_error; - if(line_break_pos == RString::npos) - { - short_error= Error; - } - else - { - short_error= Error.substr(0, line_break_pos); - } // Protect from a recursion loop resulting from a mistake in the error reporting lua. if(!InReportScriptError) { InReportScriptError= true; - ScriptErrorMessage(short_error); + ScriptErrorMessage(Error); InReportScriptError= false; } LOG->Warn(Error.c_str()); @@ -1154,6 +1144,22 @@ namespace return 1; } + static int ReportScriptError(lua_State* L) + { + RString error= "Script error occurred."; + RString error_type= "LUA_ERROR"; + if(lua_isstring(L, 1)) + { + error= SArg(1); + } + if(lua_isstring(L, 2)) + { + error_type= SArg(2); + } + LuaHelpers::ReportScriptError(error, error_type); + return 0; + } + const luaL_Reg luaTable[] = { LIST_METHOD( Trace ), @@ -1163,6 +1169,7 @@ namespace LIST_METHOD( ReadFile ), LIST_METHOD( RunWithThreadVariables ), LIST_METHOD( GetThreadVariable ), + LIST_METHOD( ReportScriptError ), { NULL, NULL } }; } diff --git a/src/ScreenDebugOverlay.cpp b/src/ScreenDebugOverlay.cpp index bb323496d7..d36355f6a2 100644 --- a/src/ScreenDebugOverlay.cpp +++ b/src/ScreenDebugOverlay.cpp @@ -1054,7 +1054,7 @@ class DebugLineToggleErrors : public IDebugLine virtual RString GetPageName() const { return "Theme"; } virtual void DoAndLog( RString &sMessageOut ) { - Message msg("ToggleErrors"); + Message msg("ToggleScriptError"); MESSAGEMAN->Broadcast(msg); IDebugLine::DoAndLog(sMessageOut); } @@ -1068,7 +1068,7 @@ class DebugLineClearErrors : public IDebugLine virtual RString GetPageName() const { return "Theme"; } virtual void DoAndLog( RString &sMessageOut ) { - Message msg("ClearErrors"); + Message msg("ClearScriptError"); MESSAGEMAN->Broadcast(msg); IDebugLine::DoAndLog(sMessageOut); } From cb168e29aab955bc5da728d7913db69f8140c148 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Fri, 18 Jul 2014 18:04:51 -0600 Subject: [PATCH 12/12] Added Common Error font for use by error reporting so that themes that replace Common Normal won't have a problem. --- Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua | 1 + Themes/_fallback/Fonts/Common Error.redir | 1 + 2 files changed, 2 insertions(+) create mode 100644 Themes/_fallback/Fonts/Common Error.redir diff --git a/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua index 830360a71f..b7b932cb04 100644 --- a/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua +++ b/Themes/_fallback/BGAnimations/ScreenSystemLayer error.lua @@ -49,6 +49,7 @@ local log_args= { Name= "ScriptError", ReplaceLinesWhenHidden= true, Times= message_time, + Font= "Common Error", } Trace("Loaded error layer.") diff --git a/Themes/_fallback/Fonts/Common Error.redir b/Themes/_fallback/Fonts/Common Error.redir new file mode 100644 index 0000000000..dde05763a4 --- /dev/null +++ b/Themes/_fallback/Fonts/Common Error.redir @@ -0,0 +1 @@ +_open sans book 24px