From fb1a251b7a1f5f27094e9d47bb7b21468b2314af Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sat, 12 Jul 2014 14:59:10 -0600 Subject: [PATCH] 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: {