diff --git a/src/GameCommand.cpp b/src/GameCommand.cpp index 8944c5be66..4424102c30 100644 --- a/src/GameCommand.cpp +++ b/src/GameCommand.cpp @@ -1,829 +1,829 @@ -#include "global.h" -#include "GameCommand.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "GameManager.h" -#include "GameState.h" -#include "AnnouncerManager.h" -#include "PlayerOptions.h" -#include "ProfileManager.h" -#include "Profile.h" -#include "StepMania.h" -#include "ScreenManager.h" -#include "PrefsManager.h" -#include "Game.h" -#include "Style.h" -#include "Foreach.h" -#include "arch/Dialog/Dialog.h" -#include "GameSoundManager.h" -#include "PlayerState.h" -#include "SongManager.h" -#include "Song.h" -#include "UnlockManager.h" -#include "LocalizedString.h" -#include "arch/ArchHooks/ArchHooks.h" -#include "ScreenPrompt.h" - -static LocalizedString COULD_NOT_LAUNCH_BROWSER( "GameCommand", "Could not launch web browser." ); - -REGISTER_CLASS_TRAITS( GameCommand, new GameCommand(*pCopy) ); - -void GameCommand::Init() -{ - m_bApplyCommitsScreens = true; - m_sName = ""; - m_sText = ""; - m_bInvalid = true; - m_iIndex = -1; - m_MultiPlayer = MultiPlayer_Invalid; - m_pStyle = NULL; - m_pm = PlayMode_Invalid; - m_dc = Difficulty_Invalid; - m_CourseDifficulty = Difficulty_Invalid; - m_sPreferredModifiers = ""; - m_sStageModifiers = ""; - m_sAnnouncer = ""; - m_sScreen = ""; - m_LuaFunction.Unset(); - m_pSong = NULL; - m_pSteps = NULL; - m_pCourse = NULL; - m_pTrail = NULL; - m_pCharacter = NULL; - m_SortOrder = SortOrder_Invalid; - m_sSoundPath = ""; - m_vsScreensToPrepare.clear(); - m_iWeightPounds = -1; - m_iGoalCalories = -1; - m_GoalType = GoalType_Invalid; - m_sProfileID = ""; - m_sUrl = ""; - m_bUrlExits = true; - - m_bStopMusic = false; - m_bApplyDefaultOptions = false; - m_bFadeMusic = false; - m_fMusicFadeOutVolume = -1.0f; - m_fMusicFadeOutSeconds = -1.0f; -} - -class SongOptions; -bool CompareSongOptions( const SongOptions &so1, const SongOptions &so2 ); - -bool GameCommand::DescribesCurrentModeForAllPlayers() const -{ - FOREACH_HumanPlayer( pn ) - if( !DescribesCurrentMode(pn) ) - return false; - - return true; -} - -bool GameCommand::DescribesCurrentMode( PlayerNumber pn ) const -{ - if( m_pm != PlayMode_Invalid && GAMESTATE->m_PlayMode != m_pm ) - return false; - if( m_pStyle && GAMESTATE->GetCurrentStyle() != m_pStyle ) - return false; - // HACK: don't compare m_dc if m_pSteps is set. This causes problems - // in ScreenSelectOptionsMaster::ImportOptions if m_PreferredDifficulty - // doesn't match the difficulty of m_pCurSteps. - if( m_pSteps == NULL && m_dc != Difficulty_Invalid ) - { - // Why is this checking for all players? - FOREACH_HumanPlayer( human ) - if( GAMESTATE->m_PreferredDifficulty[human] != m_dc ) - return false; - } - - if( m_sAnnouncer != "" && m_sAnnouncer != ANNOUNCER->GetCurAnnouncerName() ) - return false; - - if( m_sPreferredModifiers != "" ) - { - PlayerOptions po = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred(); - SongOptions so = GAMESTATE->m_SongOptions.GetPreferred(); - po.FromString( m_sPreferredModifiers ); - so.FromString( m_sPreferredModifiers ); - - if( po != GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred() ) - return false; - if( so != GAMESTATE->m_SongOptions.GetPreferred() ) - return false; - } - if( m_sStageModifiers != "" ) - { - PlayerOptions po = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetStage(); - SongOptions so = GAMESTATE->m_SongOptions.GetStage(); - po.FromString( m_sStageModifiers ); - so.FromString( m_sStageModifiers ); - - if( po != GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetStage() ) - return false; - if( so != GAMESTATE->m_SongOptions.GetStage() ) - return false; - } - - if( m_pSong && GAMESTATE->m_pCurSong.Get() != m_pSong ) - return false; - if( m_pSteps && GAMESTATE->m_pCurSteps[pn].Get() != m_pSteps ) - return false; - if( m_pCharacter && GAMESTATE->m_pCurCharacters[pn] != m_pCharacter ) - return false; - if( m_pCourse && GAMESTATE->m_pCurCourse.Get() != m_pCourse ) - return false; - if( m_pTrail && GAMESTATE->m_pCurTrail[pn].Get() != m_pTrail ) - return false; - if( !m_sSongGroup.empty() && GAMESTATE->m_sPreferredSongGroup != m_sSongGroup ) - return false; - if( m_SortOrder != SortOrder_Invalid && GAMESTATE->m_PreferredSortOrder != m_SortOrder ) - return false; - if( m_iWeightPounds != -1 && PROFILEMAN->GetProfile(pn)->m_iWeightPounds != m_iWeightPounds ) - return false; - if( m_iGoalCalories != -1 && PROFILEMAN->GetProfile(pn)->m_iGoalCalories != m_iGoalCalories ) - return false; - if( m_GoalType != GoalType_Invalid && PROFILEMAN->GetProfile(pn)->m_GoalType != m_GoalType ) - return false; - if( !m_sProfileID.empty() && ProfileManager::m_sDefaultLocalProfileID[pn].Get() != m_sProfileID ) - return false; - - return true; -} - -void GameCommand::Load( int iIndex, const Commands& cmds ) -{ - m_iIndex = iIndex; - m_bInvalid = false; - m_Commands = cmds; - - FOREACH_CONST( Command, cmds.v, cmd ) - LoadOne( *cmd ); -} - -void GameCommand::LoadOne( const Command& cmd ) -{ - RString sName = cmd.GetName(); - if( sName.empty() ) - return; - - RString sValue; - for( unsigned i = 1; i < cmd.m_vsArgs.size(); ++i ) - { - if( i > 1 ) - sValue += ","; - sValue += cmd.m_vsArgs[i]; - } - - if( sName == "style" ) - { - const Style* style = GAMEMAN->GameAndStringToStyle( GAMESTATE->m_pCurGame, sValue ); - if( style ) - m_pStyle = style; - else - m_bInvalid = true; - } - - else if( sName == "playmode" ) - { - PlayMode pm = StringToPlayMode( sValue ); - if( pm != PlayMode_Invalid ) - m_pm = pm; - else - m_bInvalid = true; - } - - else if( sName == "difficulty" ) - { - Difficulty dc = StringToDifficulty( sValue ); - if( dc != Difficulty_Invalid ) - m_dc = dc; - else - m_bInvalid = true; - } - - else if( sName == "announcer" ) - { - m_sAnnouncer = sValue; - } - - else if( sName == "name" ) - { - m_sName = sValue; - } - - else if( sName == "text" ) - { - m_sText = sValue; - } - - else if( sName == "mod" ) - { - if( m_sPreferredModifiers != "" ) - m_sPreferredModifiers += ","; - m_sPreferredModifiers += sValue; - } - - else if( sName == "stagemod" ) - { - if( m_sStageModifiers != "" ) - m_sStageModifiers += ","; - m_sStageModifiers += sValue; - } - - else if( sName == "lua" ) - { - m_LuaFunction.SetFromExpression( sValue ); - ASSERT_M( !m_LuaFunction.IsNil(), ssprintf("\"%s\" evaluated to nil", sValue.c_str()) ); - } - - else if( sName == "screen" ) - { - m_sScreen = sValue; - } - - 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; - } - } - - else if( sName == "steps" ) - { - RString sSteps = sValue; - - // This must be processed after "song" and "style" commands. - if( !m_bInvalid ) - { - 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; - } - } - } - - 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; - } - } - - else if( sName == "trail" ) - { - RString sTrail = sValue; - - // This must be processed after "course" and "style" commands. - if( !m_bInvalid ) - { - 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; - } - } - } - - else if( sName == "setenv" ) - { - if( cmd.m_vsArgs.size() == 3 ) - m_SetEnv[ cmd.m_vsArgs[1] ] = cmd.m_vsArgs[2]; - } - - else if( sName == "songgroup" ) - { - m_sSongGroup = sValue; - } - - 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; - } - } - - else if( sName == "weight" ) - { - m_iWeightPounds = StringToInt( sValue ); - } - - else if( sName == "goalcalories" ) - { - m_iGoalCalories = StringToInt( sValue ); - } - - else if( sName == "goaltype" ) - { - m_GoalType = StringToGoalType( sValue ); - } - - else if( sName == "profileid" ) - { - m_sProfileID = sValue; - } - - else if( sName == "url" ) - { - m_sUrl = sValue; - m_bUrlExits = true; - } - - else if( sName == "sound" ) - { - m_sSoundPath = sValue; - } - - else if( sName == "preparescreen" ) - { - m_vsScreensToPrepare.push_back( sValue ); - } - - else if( sName == "stopmusic" ) - { - m_bStopMusic = true; - } - - else if( sName == "applydefaultoptions" ) - { - m_bApplyDefaultOptions = true; - } - - // sm-ssc additions begin: - else if( sName == "urlnoexit" ) - { - m_sUrl = sValue; - m_bUrlExits = false; - } - - else if( sName == "setpref" ) - { - if( cmd.m_vsArgs.size() == 3 ) - { - IPreference *pPref = IPreference::GetPreferenceByName( cmd.m_vsArgs[1] ); - if( pPref == NULL ) - { - m_sInvalidReason = ssprintf("unknown preference \"%s\"", cmd.m_vsArgs[1].c_str() ); - m_bInvalid |= true; - } - pPref->FromString(cmd.m_vsArgs[2]); - } - } - - else if( sName == "fademusic" ) - { - if( cmd.m_vsArgs.size() == 3 ) - { - m_bFadeMusic = true; - m_fMusicFadeOutVolume = static_cast(atof( cmd.m_vsArgs[1] )); - m_fMusicFadeOutSeconds = static_cast(atof( cmd.m_vsArgs[2] )); - } - } - - 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" ); - } -} - -static bool AreStyleAndPlayModeCompatible( const Style *style, PlayMode pm ) -{ - if( style == NULL || pm == PlayMode_Invalid ) - return true; - - switch( pm ) - { - case PLAY_MODE_BATTLE: - case PLAY_MODE_RAVE: - // Can't play rave if there isn't enough room for two players. - // This is correct for dance (ie, no rave for solo and doubles), - // and should be okay for pump.. not sure about other game types. - // Techno Motion scales down versus arrows, though, so allow this. - if( style->m_iColsPerPlayer >= 6 && RString(GAMESTATE->m_pCurGame->m_szName) != "techno" ) - return false; - - // Don't allow battle modes if the style takes both sides. - if( style->m_StyleType==StyleType_OnePlayerTwoSides || - style->m_StyleType==StyleType_TwoPlayersSharedSides ) - return false; - default: return true; - } -} - -bool GameCommand::IsPlayable( RString *why ) const -{ - if( m_bInvalid ) - { - if( why ) - *why = m_sInvalidReason; - return false; - } - - if ( m_pStyle ) - { - /* If both sides are joined, disallow singles modes, since easy to select - * them accidentally, instead of versus mode. */ - if( m_pStyle->m_StyleType == StyleType_OnePlayerOneSide && - GAMESTATE->GetNumSidesJoined() > 1 ) - { - if( why ) - *why = "too many players joined for ONE_PLAYER_ONE_CREDIT"; - return false; - } - } - - /* Don't allow a PlayMode that's incompatible with our current Style (if set), - * and vice versa. */ - if( m_pm != PlayMode_Invalid || m_pStyle != NULL ) - { - const PlayMode pm = (m_pm != PlayMode_Invalid) ? m_pm : GAMESTATE->m_PlayMode; - const Style *style = (m_pStyle != NULL)? m_pStyle: GAMESTATE->GetCurrentStyle(); - if( !AreStyleAndPlayModeCompatible( style, pm ) ) - { - if( why ) - *why = ssprintf("mode %s is incompatible with style %s", - PlayModeToString(pm).c_str(), style->m_szName ); - - return false; - } - } - - if( !m_sScreen.CompareNoCase("ScreenEditCoursesMenu") ) - { - vector vCourses; - SONGMAN->GetAllCourses( vCourses, false ); - - if( vCourses.size() == 0 ) - { - if( why ) - *why = "No courses are installed"; - return false; - } - } - - if( (!m_sScreen.CompareNoCase("ScreenJukeboxMenu") || - !m_sScreen.CompareNoCase("ScreenEditMenu") || - !m_sScreen.CompareNoCase("ScreenEditCoursesMenu")) ) - { - if( SONGMAN->GetNumSongs() == 0 ) - { - if( why ) - *why = "No songs are installed"; - return false; - } - } - - if( !m_sPreferredModifiers.empty() ) - { - // TODO: Split this and check each modifier individually - if( UNLOCKMAN->ModifierIsLocked(m_sPreferredModifiers) ) - { if( why ) - *why = "Modifier is locked"; - return false; - } - } - - if( !m_sStageModifiers.empty() ) - { - // TODO: Split this and check each modifier individually - if( UNLOCKMAN->ModifierIsLocked(m_sStageModifiers) ) - { if( why ) - *why = "Modifier is locked"; - return false; - } - } - - return true; -} - -void GameCommand::ApplyToAllPlayers() const -{ - vector vpns; - - FOREACH_PlayerNumber( pn ) - vpns.push_back( pn ); - - Apply( vpns ); -} - -void GameCommand::Apply( PlayerNumber pn ) const -{ - vector vpns; - vpns.push_back( pn ); - Apply( vpns ); -} - -void GameCommand::Apply( const vector &vpns ) const -{ - if( m_Commands.v.size() ) - { - // We were filled using a GameCommand from metrics. Apply the options in order. - FOREACH_CONST( Command, m_Commands.v, cmd ) - { - GameCommand gc; - gc.m_bInvalid = false; - gc.m_bApplyCommitsScreens = m_bApplyCommitsScreens; - gc.LoadOne( *cmd ); - gc.ApplySelf( vpns ); - } - } - else - { - // We were filled by an OptionRowHandler in code. m_Commands isn't filled, - // so just apply the values that are already set in this. - this->ApplySelf( vpns ); - } -} - -void GameCommand::ApplySelf( const vector &vpns ) const -{ - const PlayMode OldPlayMode = GAMESTATE->m_PlayMode; - - if( m_pm != PlayMode_Invalid ) - GAMESTATE->m_PlayMode.Set( m_pm ); - - if( m_pStyle != NULL ) - { - GAMESTATE->SetCurrentStyle( m_pStyle ); - - // If only one side is joined and we picked a style that requires both - // sides, join the other side. - switch( m_pStyle->m_StyleType ) - { - case StyleType_OnePlayerOneSide: - break; - case StyleType_TwoPlayersTwoSides: - case StyleType_OnePlayerTwoSides: - case StyleType_TwoPlayersSharedSides: - { - FOREACH_PlayerNumber( p ) - GAMESTATE->JoinPlayer( p ); - } - break; - default: - FAIL_M(ssprintf("Invalid StyleType: %i", m_pStyle->m_StyleType)); - } - } - if( m_dc != Difficulty_Invalid ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - GAMESTATE->m_PreferredDifficulty[*pn].Set( m_dc ); - if( m_sAnnouncer != "" ) - ANNOUNCER->SwitchAnnouncer( m_sAnnouncer ); - if( m_sPreferredModifiers != "" ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - GAMESTATE->ApplyPreferredModifiers( *pn, m_sPreferredModifiers ); - if( m_sStageModifiers != "" ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - GAMESTATE->ApplyStageModifiers( *pn, m_sStageModifiers ); - if( m_LuaFunction.IsSet() ) - { - Lua *L = LUA->Get(); - FOREACH_CONST( PlayerNumber, vpns, pn ) - { - m_LuaFunction.PushSelf( L ); - ASSERT( !lua_isnil(L, -1) ); - - lua_pushnumber( L, *pn ); // 1st parameter - lua_call( L, 1, 0 ); // call function with 1 argument and 0 results - } - LUA->Release(L); - } - if( m_sScreen != "" && m_bApplyCommitsScreens ) - SCREENMAN->SetNewScreen( m_sScreen ); - if( m_pSong ) - { - GAMESTATE->m_pCurSong.Set( m_pSong ); - GAMESTATE->m_pPreferredSong = m_pSong; - } - if( m_pSteps ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - GAMESTATE->m_pCurSteps[*pn].Set( m_pSteps ); - if( m_pCourse ) - { - GAMESTATE->m_pCurCourse.Set( m_pCourse ); - GAMESTATE->m_pPreferredCourse = m_pCourse; - } - if( m_pTrail ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - GAMESTATE->m_pCurTrail[*pn].Set( m_pTrail ); - if( m_CourseDifficulty != Difficulty_Invalid ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - GAMESTATE->ChangePreferredCourseDifficulty( *pn, m_CourseDifficulty ); - if( m_pCharacter ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - GAMESTATE->m_pCurCharacters[*pn] = m_pCharacter; - for( map::const_iterator i = m_SetEnv.begin(); i != m_SetEnv.end(); i++ ) - { - Lua *L = LUA->Get(); - GAMESTATE->m_Environment->PushSelf(L); - lua_pushstring( L, i->first ); - lua_pushstring( L, i->second ); - lua_settable( L, -3 ); - lua_pop( L, 1 ); - LUA->Release(L); - } - if( !m_sSongGroup.empty() ) - GAMESTATE->m_sPreferredSongGroup.Set( m_sSongGroup ); - if( m_SortOrder != SortOrder_Invalid ) - GAMESTATE->m_PreferredSortOrder = m_SortOrder; - if( m_sSoundPath != "" ) - SOUND->PlayOnce( THEME->GetPathS( "", m_sSoundPath ) ); - if( m_iWeightPounds != -1 ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - PROFILEMAN->GetProfile(*pn)->m_iWeightPounds = m_iWeightPounds; - if( m_iGoalCalories != -1 ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - PROFILEMAN->GetProfile(*pn)->m_iGoalCalories = m_iGoalCalories; - if( m_GoalType != GoalType_Invalid ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - PROFILEMAN->GetProfile(*pn)->m_GoalType = m_GoalType; - if( !m_sProfileID.empty() ) - FOREACH_CONST( PlayerNumber, vpns, pn ) - ProfileManager::m_sDefaultLocalProfileID[*pn].Set( m_sProfileID ); - if( !m_sUrl.empty() ) - { - if( HOOKS->GoToURL( m_sUrl ) ) - { - if( m_bUrlExits ) - SCREENMAN->SetNewScreen( "ScreenExit" ); - } - else - ScreenPrompt::Prompt( SM_None, COULD_NOT_LAUNCH_BROWSER ); - } - - /* If we're going to stop music, do so before preparing new screens, so we - * don't stop music between preparing screens and loading screens. */ - if( m_bStopMusic ) - SOUND->StopMusic(); - if( m_bFadeMusic ) - SOUND->DimMusic(m_fMusicFadeOutVolume, m_fMusicFadeOutSeconds); - - FOREACH_CONST( RString, m_vsScreensToPrepare, s ) - SCREENMAN->PrepareScreen( *s ); - - if( m_bApplyDefaultOptions ) - { - // applying options affects only the current stage - FOREACH_PlayerNumber( p ) - { - PlayerOptions po; - GAMESTATE->GetDefaultPlayerOptions( po ); - GAMESTATE->m_pPlayerState[p]->m_PlayerOptions.Assign( ModsLevel_Stage, po ); - } - - SongOptions so; - GAMESTATE->GetDefaultSongOptions( so ); - GAMESTATE->m_SongOptions.Assign( ModsLevel_Stage, so ); - } - // HACK: Set life type to BATTERY just once here so it happens once and - // we don't override the user's changes if they back out. - if( GAMESTATE->m_PlayMode == PLAY_MODE_ONI && - GAMESTATE->m_PlayMode != OldPlayMode && - GAMESTATE->m_SongOptions.GetStage().m_LifeType == SongOptions::LIFE_BAR ) - { - SO_GROUP_ASSIGN( GAMESTATE->m_SongOptions, ModsLevel_Stage, m_LifeType, SongOptions::LIFE_BATTERY ); - } -} - -bool GameCommand::IsZero() const -{ - if( m_pm != PlayMode_Invalid || - m_pStyle != NULL || - m_dc != Difficulty_Invalid || - m_sAnnouncer != "" || - m_sPreferredModifiers != "" || - m_sStageModifiers != "" || - m_pSong != NULL || - m_pSteps != NULL || - m_pCourse != NULL || - m_pTrail != NULL || - m_pCharacter != NULL || - m_CourseDifficulty != Difficulty_Invalid || - !m_sSongGroup.empty() || - m_SortOrder != SortOrder_Invalid || - m_iWeightPounds != -1 || - m_iGoalCalories != -1 || - m_GoalType != GoalType_Invalid || - !m_sProfileID.empty() || - !m_sUrl.empty() ) - return false; - - return true; -} - -// lua start -#include "LuaBinding.h" -#include "Game.h" -#include "Steps.h" -#include "Character.h" - -/** @brief Allow Lua to have access to the GameCommand. */ -class LunaGameCommand: public Luna -{ -public: - static int GetName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sName ); return 1; } - static int GetText( T* p, lua_State *L ) { lua_pushstring(L, p->m_sText ); return 1; } - static int GetIndex( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iIndex ); return 1; } - static int GetMultiPlayer( T* p, lua_State *L ) { lua_pushnumber(L, p->m_MultiPlayer); return 1; } - static int GetStyle( T* p, lua_State *L ) { if(p->m_pStyle==NULL) lua_pushnil(L); else {Style *pStyle = (Style*)p->m_pStyle; pStyle->PushSelf(L);} return 1; } - static int GetScreen( T* p, lua_State *L ) { lua_pushstring(L, p->m_sScreen ); return 1; } - static int GetProfileID( T* p, lua_State *L ) { lua_pushstring(L, p->m_sProfileID ); return 1; } - static int GetSong( T* p, lua_State *L ) { if(p->m_pSong==NULL) lua_pushnil(L); else p->m_pSong->PushSelf(L); return 1; } - static int GetSteps( T* p, lua_State *L ) { if(p->m_pSteps==NULL) lua_pushnil(L); else p->m_pSteps->PushSelf(L); return 1; } - static int GetCourse( T* p, lua_State *L ) { if(p->m_pCourse==NULL) lua_pushnil(L); else p->m_pCourse->PushSelf(L); return 1; } - static int GetTrail( T* p, lua_State *L ) { if(p->m_pTrail==NULL) lua_pushnil(L); else p->m_pTrail->PushSelf(L); return 1; } - static int GetCharacter( T* p, lua_State *L ) { if(p->m_pCharacter==NULL) lua_pushnil(L); else p->m_pCharacter->PushSelf(L); return 1; } - static int GetSongGroup( T* p, lua_State *L ) { lua_pushstring(L, p->m_sSongGroup ); return 1; } - static int GetUrl( T* p, lua_State *L ) { lua_pushstring(L, p->m_sUrl ); return 1; } - static int GetAnnouncer( T* p, lua_State *L ) { lua_pushstring(L, p->m_sAnnouncer ); return 1; } - static int GetPreferredModifiers( T* p, lua_State *L ) { lua_pushstring(L, p->m_sPreferredModifiers ); return 1; } - static int GetStageModifiers( T* p, lua_State *L ) { lua_pushstring(L, p->m_sStageModifiers ); return 1; } - - DEFINE_METHOD( GetCourseDifficulty, m_CourseDifficulty ) - DEFINE_METHOD( GetDifficulty, m_dc ) - DEFINE_METHOD( GetPlayMode, m_pm ) - DEFINE_METHOD( GetSortOrder, m_SortOrder ) - - LunaGameCommand() - { - ADD_METHOD( GetName ); - ADD_METHOD( GetText ); - ADD_METHOD( GetIndex ); - ADD_METHOD( GetMultiPlayer ); - ADD_METHOD( GetStyle ); - ADD_METHOD( GetDifficulty ); - ADD_METHOD( GetCourseDifficulty ); - ADD_METHOD( GetScreen ); - ADD_METHOD( GetPlayMode ); - ADD_METHOD( GetProfileID ); - ADD_METHOD( GetSong ); - ADD_METHOD( GetSteps ); - ADD_METHOD( GetCourse ); - ADD_METHOD( GetTrail ); - ADD_METHOD( GetCharacter ); - ADD_METHOD( GetSongGroup ); - ADD_METHOD( GetSortOrder ); - ADD_METHOD( GetUrl ); - ADD_METHOD( GetAnnouncer ); - ADD_METHOD( GetPreferredModifiers ); - ADD_METHOD( GetStageModifiers ); - } -}; - -LUA_REGISTER_CLASS( GameCommand ) -// lua end - -/* - * (c) 2001-2004 Chris Danford, Glenn Maynard - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, provided that the above - * copyright notice(s) and this permission notice appear in all copies of - * the Software and that both the above copyright notice(s) and this - * permission notice appear in supporting documentation. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF - * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS - * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT - * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS - * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ +#include "global.h" +#include "GameCommand.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "GameManager.h" +#include "GameState.h" +#include "AnnouncerManager.h" +#include "PlayerOptions.h" +#include "ProfileManager.h" +#include "Profile.h" +#include "StepMania.h" +#include "ScreenManager.h" +#include "PrefsManager.h" +#include "Game.h" +#include "Style.h" +#include "Foreach.h" +#include "arch/Dialog/Dialog.h" +#include "GameSoundManager.h" +#include "PlayerState.h" +#include "SongManager.h" +#include "Song.h" +#include "UnlockManager.h" +#include "LocalizedString.h" +#include "arch/ArchHooks/ArchHooks.h" +#include "ScreenPrompt.h" + +static LocalizedString COULD_NOT_LAUNCH_BROWSER( "GameCommand", "Could not launch web browser." ); + +REGISTER_CLASS_TRAITS( GameCommand, new GameCommand(*pCopy) ); + +void GameCommand::Init() +{ + m_bApplyCommitsScreens = true; + m_sName = ""; + m_sText = ""; + m_bInvalid = true; + m_iIndex = -1; + m_MultiPlayer = MultiPlayer_Invalid; + m_pStyle = NULL; + m_pm = PlayMode_Invalid; + m_dc = Difficulty_Invalid; + m_CourseDifficulty = Difficulty_Invalid; + m_sPreferredModifiers = ""; + m_sStageModifiers = ""; + m_sAnnouncer = ""; + m_sScreen = ""; + m_LuaFunction.Unset(); + m_pSong = NULL; + m_pSteps = NULL; + m_pCourse = NULL; + m_pTrail = NULL; + m_pCharacter = NULL; + m_SortOrder = SortOrder_Invalid; + m_sSoundPath = ""; + m_vsScreensToPrepare.clear(); + m_iWeightPounds = -1; + m_iGoalCalories = -1; + m_GoalType = GoalType_Invalid; + m_sProfileID = ""; + m_sUrl = ""; + m_bUrlExits = true; + + m_bStopMusic = false; + m_bApplyDefaultOptions = false; + m_bFadeMusic = false; + m_fMusicFadeOutVolume = -1.0f; + m_fMusicFadeOutSeconds = -1.0f; +} + +class SongOptions; +bool CompareSongOptions( const SongOptions &so1, const SongOptions &so2 ); + +bool GameCommand::DescribesCurrentModeForAllPlayers() const +{ + FOREACH_HumanPlayer( pn ) + if( !DescribesCurrentMode(pn) ) + return false; + + return true; +} + +bool GameCommand::DescribesCurrentMode( PlayerNumber pn ) const +{ + if( m_pm != PlayMode_Invalid && GAMESTATE->m_PlayMode != m_pm ) + return false; + if( m_pStyle && GAMESTATE->GetCurrentStyle() != m_pStyle ) + return false; + // HACK: don't compare m_dc if m_pSteps is set. This causes problems + // in ScreenSelectOptionsMaster::ImportOptions if m_PreferredDifficulty + // doesn't match the difficulty of m_pCurSteps. + if( m_pSteps == NULL && m_dc != Difficulty_Invalid ) + { + // Why is this checking for all players? + FOREACH_HumanPlayer( human ) + if( GAMESTATE->m_PreferredDifficulty[human] != m_dc ) + return false; + } + + if( m_sAnnouncer != "" && m_sAnnouncer != ANNOUNCER->GetCurAnnouncerName() ) + return false; + + if( m_sPreferredModifiers != "" ) + { + PlayerOptions po = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred(); + SongOptions so = GAMESTATE->m_SongOptions.GetPreferred(); + po.FromString( m_sPreferredModifiers ); + so.FromString( m_sPreferredModifiers ); + + if( po != GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred() ) + return false; + if( so != GAMESTATE->m_SongOptions.GetPreferred() ) + return false; + } + if( m_sStageModifiers != "" ) + { + PlayerOptions po = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetStage(); + SongOptions so = GAMESTATE->m_SongOptions.GetStage(); + po.FromString( m_sStageModifiers ); + so.FromString( m_sStageModifiers ); + + if( po != GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetStage() ) + return false; + if( so != GAMESTATE->m_SongOptions.GetStage() ) + return false; + } + + if( m_pSong && GAMESTATE->m_pCurSong.Get() != m_pSong ) + return false; + if( m_pSteps && GAMESTATE->m_pCurSteps[pn].Get() != m_pSteps ) + return false; + if( m_pCharacter && GAMESTATE->m_pCurCharacters[pn] != m_pCharacter ) + return false; + if( m_pCourse && GAMESTATE->m_pCurCourse.Get() != m_pCourse ) + return false; + if( m_pTrail && GAMESTATE->m_pCurTrail[pn].Get() != m_pTrail ) + return false; + if( !m_sSongGroup.empty() && GAMESTATE->m_sPreferredSongGroup != m_sSongGroup ) + return false; + if( m_SortOrder != SortOrder_Invalid && GAMESTATE->m_PreferredSortOrder != m_SortOrder ) + return false; + if( m_iWeightPounds != -1 && PROFILEMAN->GetProfile(pn)->m_iWeightPounds != m_iWeightPounds ) + return false; + if( m_iGoalCalories != -1 && PROFILEMAN->GetProfile(pn)->m_iGoalCalories != m_iGoalCalories ) + return false; + if( m_GoalType != GoalType_Invalid && PROFILEMAN->GetProfile(pn)->m_GoalType != m_GoalType ) + return false; + if( !m_sProfileID.empty() && ProfileManager::m_sDefaultLocalProfileID[pn].Get() != m_sProfileID ) + return false; + + return true; +} + +void GameCommand::Load( int iIndex, const Commands& cmds ) +{ + m_iIndex = iIndex; + m_bInvalid = false; + m_Commands = cmds; + + for (Command const &cmd : cmds.v) + LoadOne( cmd ); +} + +void GameCommand::LoadOne( const Command& cmd ) +{ + RString sName = cmd.GetName(); + if( sName.empty() ) + return; + + RString sValue; + for( unsigned i = 1; i < cmd.m_vsArgs.size(); ++i ) + { + if( i > 1 ) + sValue += ","; + sValue += cmd.m_vsArgs[i]; + } + + if( sName == "style" ) + { + const Style* style = GAMEMAN->GameAndStringToStyle( GAMESTATE->m_pCurGame, sValue ); + if( style ) + m_pStyle = style; + else + m_bInvalid = true; + } + + else if( sName == "playmode" ) + { + PlayMode pm = StringToPlayMode( sValue ); + if( pm != PlayMode_Invalid ) + m_pm = pm; + else + m_bInvalid = true; + } + + else if( sName == "difficulty" ) + { + Difficulty dc = StringToDifficulty( sValue ); + if( dc != Difficulty_Invalid ) + m_dc = dc; + else + m_bInvalid = true; + } + + else if( sName == "announcer" ) + { + m_sAnnouncer = sValue; + } + + else if( sName == "name" ) + { + m_sName = sValue; + } + + else if( sName == "text" ) + { + m_sText = sValue; + } + + else if( sName == "mod" ) + { + if( m_sPreferredModifiers != "" ) + m_sPreferredModifiers += ","; + m_sPreferredModifiers += sValue; + } + + else if( sName == "stagemod" ) + { + if( m_sStageModifiers != "" ) + m_sStageModifiers += ","; + m_sStageModifiers += sValue; + } + + else if( sName == "lua" ) + { + m_LuaFunction.SetFromExpression( sValue ); + ASSERT_M( !m_LuaFunction.IsNil(), ssprintf("\"%s\" evaluated to nil", sValue.c_str()) ); + } + + else if( sName == "screen" ) + { + m_sScreen = sValue; + } + + 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; + } + } + + else if( sName == "steps" ) + { + RString sSteps = sValue; + + // This must be processed after "song" and "style" commands. + if( !m_bInvalid ) + { + 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; + } + } + } + + 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; + } + } + + else if( sName == "trail" ) + { + RString sTrail = sValue; + + // This must be processed after "course" and "style" commands. + if( !m_bInvalid ) + { + 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; + } + } + } + + else if( sName == "setenv" ) + { + if( cmd.m_vsArgs.size() == 3 ) + m_SetEnv[ cmd.m_vsArgs[1] ] = cmd.m_vsArgs[2]; + } + + else if( sName == "songgroup" ) + { + m_sSongGroup = sValue; + } + + 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; + } + } + + else if( sName == "weight" ) + { + m_iWeightPounds = StringToInt( sValue ); + } + + else if( sName == "goalcalories" ) + { + m_iGoalCalories = StringToInt( sValue ); + } + + else if( sName == "goaltype" ) + { + m_GoalType = StringToGoalType( sValue ); + } + + else if( sName == "profileid" ) + { + m_sProfileID = sValue; + } + + else if( sName == "url" ) + { + m_sUrl = sValue; + m_bUrlExits = true; + } + + else if( sName == "sound" ) + { + m_sSoundPath = sValue; + } + + else if( sName == "preparescreen" ) + { + m_vsScreensToPrepare.push_back( sValue ); + } + + else if( sName == "stopmusic" ) + { + m_bStopMusic = true; + } + + else if( sName == "applydefaultoptions" ) + { + m_bApplyDefaultOptions = true; + } + + // sm-ssc additions begin: + else if( sName == "urlnoexit" ) + { + m_sUrl = sValue; + m_bUrlExits = false; + } + + else if( sName == "setpref" ) + { + if( cmd.m_vsArgs.size() == 3 ) + { + IPreference *pPref = IPreference::GetPreferenceByName( cmd.m_vsArgs[1] ); + if( pPref == NULL ) + { + m_sInvalidReason = ssprintf("unknown preference \"%s\"", cmd.m_vsArgs[1].c_str() ); + m_bInvalid |= true; + } + pPref->FromString(cmd.m_vsArgs[2]); + } + } + + else if( sName == "fademusic" ) + { + if( cmd.m_vsArgs.size() == 3 ) + { + m_bFadeMusic = true; + m_fMusicFadeOutVolume = static_cast(atof( cmd.m_vsArgs[1] )); + m_fMusicFadeOutSeconds = static_cast(atof( cmd.m_vsArgs[2] )); + } + } + + 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" ); + } +} + +static bool AreStyleAndPlayModeCompatible( const Style *style, PlayMode pm ) +{ + if( style == NULL || pm == PlayMode_Invalid ) + return true; + + switch( pm ) + { + case PLAY_MODE_BATTLE: + case PLAY_MODE_RAVE: + // Can't play rave if there isn't enough room for two players. + // This is correct for dance (ie, no rave for solo and doubles), + // and should be okay for pump.. not sure about other game types. + // Techno Motion scales down versus arrows, though, so allow this. + if( style->m_iColsPerPlayer >= 6 && RString(GAMESTATE->m_pCurGame->m_szName) != "techno" ) + return false; + + // Don't allow battle modes if the style takes both sides. + if( style->m_StyleType==StyleType_OnePlayerTwoSides || + style->m_StyleType==StyleType_TwoPlayersSharedSides ) + return false; + default: return true; + } +} + +bool GameCommand::IsPlayable( RString *why ) const +{ + if( m_bInvalid ) + { + if( why ) + *why = m_sInvalidReason; + return false; + } + + if ( m_pStyle ) + { + /* If both sides are joined, disallow singles modes, since easy to select + * them accidentally, instead of versus mode. */ + if( m_pStyle->m_StyleType == StyleType_OnePlayerOneSide && + GAMESTATE->GetNumSidesJoined() > 1 ) + { + if( why ) + *why = "too many players joined for ONE_PLAYER_ONE_CREDIT"; + return false; + } + } + + /* Don't allow a PlayMode that's incompatible with our current Style (if set), + * and vice versa. */ + if( m_pm != PlayMode_Invalid || m_pStyle != NULL ) + { + const PlayMode pm = (m_pm != PlayMode_Invalid) ? m_pm : GAMESTATE->m_PlayMode; + const Style *style = (m_pStyle != NULL)? m_pStyle: GAMESTATE->GetCurrentStyle(); + if( !AreStyleAndPlayModeCompatible( style, pm ) ) + { + if( why ) + *why = ssprintf("mode %s is incompatible with style %s", + PlayModeToString(pm).c_str(), style->m_szName ); + + return false; + } + } + + if( !m_sScreen.CompareNoCase("ScreenEditCoursesMenu") ) + { + vector vCourses; + SONGMAN->GetAllCourses( vCourses, false ); + + if( vCourses.size() == 0 ) + { + if( why ) + *why = "No courses are installed"; + return false; + } + } + + if( (!m_sScreen.CompareNoCase("ScreenJukeboxMenu") || + !m_sScreen.CompareNoCase("ScreenEditMenu") || + !m_sScreen.CompareNoCase("ScreenEditCoursesMenu")) ) + { + if( SONGMAN->GetNumSongs() == 0 ) + { + if( why ) + *why = "No songs are installed"; + return false; + } + } + + if( !m_sPreferredModifiers.empty() ) + { + // TODO: Split this and check each modifier individually + if( UNLOCKMAN->ModifierIsLocked(m_sPreferredModifiers) ) + { if( why ) + *why = "Modifier is locked"; + return false; + } + } + + if( !m_sStageModifiers.empty() ) + { + // TODO: Split this and check each modifier individually + if( UNLOCKMAN->ModifierIsLocked(m_sStageModifiers) ) + { if( why ) + *why = "Modifier is locked"; + return false; + } + } + + return true; +} + +void GameCommand::ApplyToAllPlayers() const +{ + vector vpns; + + FOREACH_PlayerNumber( pn ) + vpns.push_back( pn ); + + Apply( vpns ); +} + +void GameCommand::Apply( PlayerNumber pn ) const +{ + vector vpns; + vpns.push_back( pn ); + Apply( vpns ); +} + +void GameCommand::Apply( const vector &vpns ) const +{ + if( m_Commands.v.size() ) + { + // We were filled using a GameCommand from metrics. Apply the options in order. + for (Command const &cmd : m_Commands.v) + { + GameCommand gc; + gc.m_bInvalid = false; + gc.m_bApplyCommitsScreens = m_bApplyCommitsScreens; + gc.LoadOne( cmd ); + gc.ApplySelf( vpns ); + } + } + else + { + // We were filled by an OptionRowHandler in code. m_Commands isn't filled, + // so just apply the values that are already set in this. + this->ApplySelf( vpns ); + } +} + +void GameCommand::ApplySelf( const vector &vpns ) const +{ + const PlayMode OldPlayMode = GAMESTATE->m_PlayMode; + + if( m_pm != PlayMode_Invalid ) + GAMESTATE->m_PlayMode.Set( m_pm ); + + if( m_pStyle != NULL ) + { + GAMESTATE->SetCurrentStyle( m_pStyle ); + + // If only one side is joined and we picked a style that requires both + // sides, join the other side. + switch( m_pStyle->m_StyleType ) + { + case StyleType_OnePlayerOneSide: + break; + case StyleType_TwoPlayersTwoSides: + case StyleType_OnePlayerTwoSides: + case StyleType_TwoPlayersSharedSides: + { + FOREACH_PlayerNumber( p ) + GAMESTATE->JoinPlayer( p ); + } + break; + default: + FAIL_M(ssprintf("Invalid StyleType: %i", m_pStyle->m_StyleType)); + } + } + if( m_dc != Difficulty_Invalid ) + for (PlayerNumber const &pn : vpns) + GAMESTATE->m_PreferredDifficulty[pn].Set( m_dc ); + if( m_sAnnouncer != "" ) + ANNOUNCER->SwitchAnnouncer( m_sAnnouncer ); + if( m_sPreferredModifiers != "" ) + for (PlayerNumber const &pn : vpns) + GAMESTATE->ApplyPreferredModifiers( pn, m_sPreferredModifiers ); + if( m_sStageModifiers != "" ) + for (PlayerNumber const &pn : vpns) + GAMESTATE->ApplyStageModifiers( pn, m_sStageModifiers ); + if( m_LuaFunction.IsSet() ) + { + Lua *L = LUA->Get(); + for (PlayerNumber const &pn : vpns) + { + m_LuaFunction.PushSelf( L ); + ASSERT( !lua_isnil(L, -1) ); + + lua_pushnumber( L, pn ); // 1st parameter + lua_call( L, 1, 0 ); // call function with 1 argument and 0 results + } + LUA->Release(L); + } + if( m_sScreen != "" && m_bApplyCommitsScreens ) + SCREENMAN->SetNewScreen( m_sScreen ); + if( m_pSong ) + { + GAMESTATE->m_pCurSong.Set( m_pSong ); + GAMESTATE->m_pPreferredSong = m_pSong; + } + if( m_pSteps ) + for (PlayerNumber const &pn : vpns) + GAMESTATE->m_pCurSteps[pn].Set( m_pSteps ); + if( m_pCourse ) + { + GAMESTATE->m_pCurCourse.Set( m_pCourse ); + GAMESTATE->m_pPreferredCourse = m_pCourse; + } + if( m_pTrail ) + for (PlayerNumber const &pn : vpns) + GAMESTATE->m_pCurTrail[pn].Set( m_pTrail ); + if( m_CourseDifficulty != Difficulty_Invalid ) + for (PlayerNumber const &pn : vpns) + GAMESTATE->ChangePreferredCourseDifficulty( pn, m_CourseDifficulty ); + if( m_pCharacter ) + for (PlayerNumber const &pn : vpns) + GAMESTATE->m_pCurCharacters[pn] = m_pCharacter; + for( map::const_iterator i = m_SetEnv.begin(); i != m_SetEnv.end(); i++ ) + { + Lua *L = LUA->Get(); + GAMESTATE->m_Environment->PushSelf(L); + lua_pushstring( L, i->first ); + lua_pushstring( L, i->second ); + lua_settable( L, -3 ); + lua_pop( L, 1 ); + LUA->Release(L); + } + if( !m_sSongGroup.empty() ) + GAMESTATE->m_sPreferredSongGroup.Set( m_sSongGroup ); + if( m_SortOrder != SortOrder_Invalid ) + GAMESTATE->m_PreferredSortOrder = m_SortOrder; + if( m_sSoundPath != "" ) + SOUND->PlayOnce( THEME->GetPathS( "", m_sSoundPath ) ); + if( m_iWeightPounds != -1 ) + for (PlayerNumber const &pn : vpns) + PROFILEMAN->GetProfile(pn)->m_iWeightPounds = m_iWeightPounds; + if( m_iGoalCalories != -1 ) + for (PlayerNumber const &pn : vpns) + PROFILEMAN->GetProfile(pn)->m_iGoalCalories = m_iGoalCalories; + if( m_GoalType != GoalType_Invalid ) + for (PlayerNumber const &pn : vpns) + PROFILEMAN->GetProfile(pn)->m_GoalType = m_GoalType; + if( !m_sProfileID.empty() ) + for (PlayerNumber const &pn : vpns) + ProfileManager::m_sDefaultLocalProfileID[pn].Set( m_sProfileID ); + if( !m_sUrl.empty() ) + { + if( HOOKS->GoToURL( m_sUrl ) ) + { + if( m_bUrlExits ) + SCREENMAN->SetNewScreen( "ScreenExit" ); + } + else + ScreenPrompt::Prompt( SM_None, COULD_NOT_LAUNCH_BROWSER ); + } + + /* If we're going to stop music, do so before preparing new screens, so we + * don't stop music between preparing screens and loading screens. */ + if( m_bStopMusic ) + SOUND->StopMusic(); + if( m_bFadeMusic ) + SOUND->DimMusic(m_fMusicFadeOutVolume, m_fMusicFadeOutSeconds); + + for (RString const &s : m_vsScreensToPrepare) + SCREENMAN->PrepareScreen( s ); + + if( m_bApplyDefaultOptions ) + { + // applying options affects only the current stage + FOREACH_PlayerNumber( p ) + { + PlayerOptions po; + GAMESTATE->GetDefaultPlayerOptions( po ); + GAMESTATE->m_pPlayerState[p]->m_PlayerOptions.Assign( ModsLevel_Stage, po ); + } + + SongOptions so; + GAMESTATE->GetDefaultSongOptions( so ); + GAMESTATE->m_SongOptions.Assign( ModsLevel_Stage, so ); + } + // HACK: Set life type to BATTERY just once here so it happens once and + // we don't override the user's changes if they back out. + if( GAMESTATE->m_PlayMode == PLAY_MODE_ONI && + GAMESTATE->m_PlayMode != OldPlayMode && + GAMESTATE->m_SongOptions.GetStage().m_LifeType == SongOptions::LIFE_BAR ) + { + SO_GROUP_ASSIGN( GAMESTATE->m_SongOptions, ModsLevel_Stage, m_LifeType, SongOptions::LIFE_BATTERY ); + } +} + +bool GameCommand::IsZero() const +{ + if( m_pm != PlayMode_Invalid || + m_pStyle != NULL || + m_dc != Difficulty_Invalid || + m_sAnnouncer != "" || + m_sPreferredModifiers != "" || + m_sStageModifiers != "" || + m_pSong != NULL || + m_pSteps != NULL || + m_pCourse != NULL || + m_pTrail != NULL || + m_pCharacter != NULL || + m_CourseDifficulty != Difficulty_Invalid || + !m_sSongGroup.empty() || + m_SortOrder != SortOrder_Invalid || + m_iWeightPounds != -1 || + m_iGoalCalories != -1 || + m_GoalType != GoalType_Invalid || + !m_sProfileID.empty() || + !m_sUrl.empty() ) + return false; + + return true; +} + +// lua start +#include "LuaBinding.h" +#include "Game.h" +#include "Steps.h" +#include "Character.h" + +/** @brief Allow Lua to have access to the GameCommand. */ +class LunaGameCommand: public Luna +{ +public: + static int GetName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sName ); return 1; } + static int GetText( T* p, lua_State *L ) { lua_pushstring(L, p->m_sText ); return 1; } + static int GetIndex( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iIndex ); return 1; } + static int GetMultiPlayer( T* p, lua_State *L ) { lua_pushnumber(L, p->m_MultiPlayer); return 1; } + static int GetStyle( T* p, lua_State *L ) { if(p->m_pStyle==NULL) lua_pushnil(L); else {Style *pStyle = (Style*)p->m_pStyle; pStyle->PushSelf(L);} return 1; } + static int GetScreen( T* p, lua_State *L ) { lua_pushstring(L, p->m_sScreen ); return 1; } + static int GetProfileID( T* p, lua_State *L ) { lua_pushstring(L, p->m_sProfileID ); return 1; } + static int GetSong( T* p, lua_State *L ) { if(p->m_pSong==NULL) lua_pushnil(L); else p->m_pSong->PushSelf(L); return 1; } + static int GetSteps( T* p, lua_State *L ) { if(p->m_pSteps==NULL) lua_pushnil(L); else p->m_pSteps->PushSelf(L); return 1; } + static int GetCourse( T* p, lua_State *L ) { if(p->m_pCourse==NULL) lua_pushnil(L); else p->m_pCourse->PushSelf(L); return 1; } + static int GetTrail( T* p, lua_State *L ) { if(p->m_pTrail==NULL) lua_pushnil(L); else p->m_pTrail->PushSelf(L); return 1; } + static int GetCharacter( T* p, lua_State *L ) { if(p->m_pCharacter==NULL) lua_pushnil(L); else p->m_pCharacter->PushSelf(L); return 1; } + static int GetSongGroup( T* p, lua_State *L ) { lua_pushstring(L, p->m_sSongGroup ); return 1; } + static int GetUrl( T* p, lua_State *L ) { lua_pushstring(L, p->m_sUrl ); return 1; } + static int GetAnnouncer( T* p, lua_State *L ) { lua_pushstring(L, p->m_sAnnouncer ); return 1; } + static int GetPreferredModifiers( T* p, lua_State *L ) { lua_pushstring(L, p->m_sPreferredModifiers ); return 1; } + static int GetStageModifiers( T* p, lua_State *L ) { lua_pushstring(L, p->m_sStageModifiers ); return 1; } + + DEFINE_METHOD( GetCourseDifficulty, m_CourseDifficulty ) + DEFINE_METHOD( GetDifficulty, m_dc ) + DEFINE_METHOD( GetPlayMode, m_pm ) + DEFINE_METHOD( GetSortOrder, m_SortOrder ) + + LunaGameCommand() + { + ADD_METHOD( GetName ); + ADD_METHOD( GetText ); + ADD_METHOD( GetIndex ); + ADD_METHOD( GetMultiPlayer ); + ADD_METHOD( GetStyle ); + ADD_METHOD( GetDifficulty ); + ADD_METHOD( GetCourseDifficulty ); + ADD_METHOD( GetScreen ); + ADD_METHOD( GetPlayMode ); + ADD_METHOD( GetProfileID ); + ADD_METHOD( GetSong ); + ADD_METHOD( GetSteps ); + ADD_METHOD( GetCourse ); + ADD_METHOD( GetTrail ); + ADD_METHOD( GetCharacter ); + ADD_METHOD( GetSongGroup ); + ADD_METHOD( GetSortOrder ); + ADD_METHOD( GetUrl ); + ADD_METHOD( GetAnnouncer ); + ADD_METHOD( GetPreferredModifiers ); + ADD_METHOD( GetStageModifiers ); + } +}; + +LUA_REGISTER_CLASS( GameCommand ) +// lua end + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */