#include "global.h" #include "GameState.h" #include "Actor.h" #include "ActorUtil.h" #include "AdjustSync.h" #include "AnnouncerManager.h" #include "Bookkeeper.h" #include "Character.h" #include "CharacterManager.h" #include "CommonMetrics.h" #include "Course.h" #include "CryptManager.h" #include "Game.h" #include "GameCommand.h" #include "GameConstantsAndTypes.h" #include "GameManager.h" #include "GamePreferences.h" #include "HighScore.h" #include "LightsManager.h" #include "LuaReference.h" #include "MessageManager.h" #include "MemoryCardManager.h" #include "NoteData.h" #include "NoteSkinManager.h" #include "PlayerState.h" #include "PrefsManager.h" #include "Profile.h" #include "ProfileManager.h" #include "RageFile.h" #include "RageFileManager.h" #include "RageLog.h" #include "RageUtil.h" #include "Song.h" #include "SongManager.h" #include "SongUtil.h" #include "StatsManager.h" #include "StepMania.h" #include "Steps.h" #include "Style.h" #include "ThemeManager.h" #include "UnlockManager.h" #include "ScreenManager.h" #include "Screen.h" #include #include #include #include #include GameState* GAMESTATE = nullptr; // global and accessible from anywhere in our program #define NAME_BLACKLIST_FILE "/Data/NamesBlacklist.txt" class GameStateMessageHandler: public MessageSubscriber { void HandleMessage( const Message &msg ) { if( msg.GetName() == "RefreshCreditText" ) { RString sJoined; FOREACH_HumanPlayer( pn ) { if( sJoined != "" ) sJoined += ", "; sJoined += ssprintf( "P%i", pn+1 ); } if( sJoined == "" ) sJoined = "none"; LOG->MapLog( "JOINED", "Players joined: %s", sJoined.c_str() ); } } }; struct GameStateImpl { GameStateMessageHandler m_Subscriber; GameStateImpl() { m_Subscriber.SubscribeToMessage( "RefreshCreditText" ); } }; static GameStateImpl *g_pImpl = nullptr; ThemeMetric ALLOW_LATE_JOIN("GameState","AllowLateJoin"); ThemeMetric USE_NAME_BLACKLIST("GameState","UseNameBlacklist"); ThemeMetric DEFAULT_SORT ("GameState","DefaultSort"); SortOrder GetDefaultSort() { return StringToSortOrder( DEFAULT_SORT ); } ThemeMetric DEFAULT_SONG ("GameState","DefaultSong"); Song* GameState::GetDefaultSong() const { SongID sid; sid.FromString( DEFAULT_SONG ); return sid.ToSong(); } static const ThemeMetric EDIT_ALLOWED_FOR_EXTRA ("GameState","EditAllowedForExtra"); static const ThemeMetric MIN_DIFFICULTY_FOR_EXTRA ("GameState","MinDifficultyForExtra"); static const ThemeMetric GRADE_TIER_FOR_EXTRA_1 ("GameState","GradeTierForExtra1"); static const ThemeMetric ALLOW_EXTRA_2 ("GameState","AllowExtra2"); static const ThemeMetric GRADE_TIER_FOR_EXTRA_2 ("GameState","GradeTierForExtra2"); static ThemeMetric ARE_STAGE_PLAYER_MODS_FORCED ("GameState","AreStagePlayerModsForced"); static ThemeMetric ARE_STAGE_SONG_MODS_FORCED ("GameState","AreStageSongModsForced"); static Preference g_Premium( "Premium", Premium_DoubleFor1Credit ); Preference GameState::m_bAutoJoin( "AutoJoin", false ); GameState::GameState() : processedTiming(nullptr), m_pCurGame( Message_CurrentGameChanged ), m_pCurStyle( Message_CurrentStyleChanged ), m_PlayMode( Message_PlayModeChanged ), m_iCoins( Message_CoinsChanged ), m_sPreferredSongGroup( Message_PreferredSongGroupChanged ), m_sPreferredCourseGroup( Message_PreferredCourseGroupChanged ), m_PreferredStepsType( Message_PreferredStepsTypeChanged ), m_PreferredDifficulty( Message_PreferredDifficultyP1Changed ), m_PreferredCourseDifficulty( Message_PreferredCourseDifficultyP1Changed ), m_SortOrder( Message_SortOrderChanged ), m_pCurSong( Message_CurrentSongChanged ), m_pCurSteps( Message_CurrentStepsP1Changed ), m_pCurCourse( Message_CurrentCourseChanged ), m_pCurTrail( Message_CurrentTrailP1Changed ), m_bGameplayLeadIn( Message_GameplayLeadInChanged ), m_bDidModeChangeNoteSkin( false ), m_bIsUsingStepTiming( true ), m_bInStepEditor( false ), m_stEdit( Message_EditStepsTypeChanged ), m_cdEdit( Message_EditCourseDifficultyChanged ), m_pEditSourceSteps( Message_EditSourceStepsChanged ), m_stEditSource( Message_EditSourceStepsTypeChanged ), m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ), m_sEditLocalProfileID( Message_EditLocalProfileIDChanged ) { g_pImpl = new GameStateImpl; m_pCurStyle.Set(nullptr); FOREACH_PlayerNumber(rpn) { m_SeparatedStyles[rpn]= nullptr; } m_pCurGame.Set(nullptr); m_iCoins.Set( 0 ); m_timeGameStarted.SetZero(); m_bDemonstrationOrJukebox = false; m_iNumTimesThroughAttract = -1; // initial screen will bump this up to 0 m_iStageSeed = m_iGameSeed = 0; m_PlayMode.Set( PlayMode_Invalid ); // used by IsPlayerEnabled before the first screen FOREACH_PlayerNumber( p ) m_bSideIsJoined[p] = false; // used by GetNumSidesJoined before the first screen FOREACH_PlayerNumber( p ) { m_pPlayerState[p] = new PlayerState; m_pPlayerState[p]->SetPlayerNumber(p); } FOREACH_MultiPlayer( p ) { m_pMultiPlayerState[p] = new PlayerState; m_pMultiPlayerState[p]->SetPlayerNumber(PLAYER_1); m_pMultiPlayerState[p]->m_mp = p; } m_Environment = new LuaTable; m_bDopefish = false; sLastOpenSection = ""; sExpandedSectionName = ""; // Don't reset yet; let the first screen do it, so we can use PREFSMAN and THEME. //Reset(); // Register with Lua. { Lua *L = LUA->Get(); lua_pushstring( L, "GAMESTATE" ); this->PushSelf( L ); lua_settable( L, LUA_GLOBALSINDEX ); LUA->Release( L ); } } GameState::~GameState() { // Unregister with Lua. LUA->UnsetGlobal( "GAMESTATE" ); FOREACH_PlayerNumber( p ) RageUtil::SafeDelete( m_pPlayerState[p] ); FOREACH_MultiPlayer( p ) RageUtil::SafeDelete( m_pMultiPlayerState[p] ); RageUtil::SafeDelete( m_Environment ); RageUtil::SafeDelete( g_pImpl ); RageUtil::SafeDelete( processedTiming ); } PlayerNumber GameState::GetMasterPlayerNumber() const { return this->masterPlayerNumber; } void GameState::SetMasterPlayerNumber(const PlayerNumber p) { this->masterPlayerNumber = p; } TimingData * GameState::GetProcessedTimingData() const { return this->processedTiming; } void GameState::SetProcessedTimingData(TimingData * t) { this->processedTiming = t; } void GameState::ApplyGameCommand( const RString &sCommand, PlayerNumber pn ) { GameCommand m; m.Load( 0, ParseCommands(sCommand) ); RString sWhy; if( !m.IsPlayable(&sWhy) ) { LuaHelpers::ReportScriptErrorFmt("Can't apply GameCommand \"%s\": %s", sCommand.c_str(), sWhy.c_str()); return; } if( pn == PLAYER_INVALID ) m.ApplyToAllPlayers(); else m.Apply( pn ); } void GameState::ApplyCmdline() { // We need to join players before we can set the style. RString sPlayer; for( int i = 0; GetCommandlineArgument( "player", &sPlayer, i ); ++i ) { int pn = StringToInt( sPlayer )-1; if( !IsAnInt( sPlayer ) || pn < 0 || pn >= NUM_PLAYERS ) RageException::Throw( "Invalid argument \"--player=%s\".", sPlayer.c_str() ); JoinPlayer( (PlayerNumber) pn ); } RString sMode; for( int i = 0; GetCommandlineArgument( "mode", &sMode, i ); ++i ) { ApplyGameCommand( sMode ); } } void GameState::ResetPlayer( PlayerNumber pn ) { m_PreferredStepsType.Set( StepsType_Invalid ); m_PreferredDifficulty[pn].Set( Difficulty_Invalid ); m_PreferredCourseDifficulty[pn].Set( Difficulty_Medium ); m_iPlayerStageTokens[pn] = 0; m_iAwardedExtraStages[pn] = 0; m_pCurSteps[pn].Set(nullptr); m_pCurTrail[pn].Set(nullptr); m_pPlayerState[pn]->Reset(); PROFILEMAN->UnloadProfile( pn ); ResetPlayerOptions(pn); } void GameState::ResetPlayerOptions( PlayerNumber pn ) { PlayerOptions po; GetDefaultPlayerOptions( po ); m_pPlayerState[pn]->m_PlayerOptions.Assign( ModsLevel_Preferred, po ); } void GameState::Reset() { this->SetMasterPlayerNumber(PLAYER_INVALID); // must initialize for UnjoinPlayer FOREACH_PlayerNumber( pn ) UnjoinPlayer( pn ); ASSERT( THEME != nullptr ); m_timeGameStarted.SetZero(); SetCurrentStyle( nullptr, PLAYER_INVALID ); FOREACH_MultiPlayer( p ) m_MultiPlayerStatus[p] = MultiPlayerStatus_NotJoined; FOREACH_PlayerNumber( pn ) MEMCARDMAN->UnlockCard( pn ); m_bMultiplayer = false; m_iNumMultiplayerNoteFields = 1; *m_Environment = LuaTable(); m_sPreferredSongGroup.Set( GROUP_ALL ); m_sPreferredCourseGroup.Set( GROUP_ALL ); m_bFailTypeWasExplicitlySet = false; m_SortOrder.Set( SortOrder_Invalid ); m_PreferredSortOrder = GetDefaultSort(); m_PlayMode.Set( PlayMode_Invalid ); m_EditMode = EditMode_Invalid; m_bDemonstrationOrJukebox = false; m_bJukeboxUsesModifiers = false; m_iCurrentStageIndex = 0; m_bGameplayLeadIn.Set( false ); m_iNumStagesOfThisSong = 0; m_bLoadingNextSong = false; NOTESKIN->RefreshNoteSkinData( m_pCurGame ); m_iGameSeed = rand(); m_iStageSeed = rand(); m_AdjustTokensBySongCostForFinalStageCheck= true; m_pCurSong.Set( GetDefaultSong() ); m_pPreferredSong = nullptr; m_pCurCourse.Set(nullptr); m_pPreferredCourse = nullptr; FOREACH_MultiPlayer( p ) m_pMultiPlayerState[p]->Reset(); m_SongOptions.Init(); m_paused= false; ResetMusicStatistics(); ResetStageStatistics(); AdjustSync::ResetOriginalSyncData(); SONGMAN->UpdatePopular(); SONGMAN->UpdateShuffled(); /* We may have cached trails from before everything was loaded (eg. from * before SongManager::UpdatePopular could be called). Erase the cache. */ SONGMAN->RegenerateNonFixedCourses(); STATSMAN->Reset(); FOREACH_PlayerNumber(p) { if( PREFSMAN->m_ShowDancingCharacters == SDC_Random ) m_pCurCharacters[p] = CHARMAN->GetRandomCharacter(); else m_pCurCharacters[p] = CHARMAN->GetDefaultCharacter(); ASSERT( m_pCurCharacters[p] != nullptr ); } m_bTemporaryEventMode = false; LIGHTSMAN->SetLightsMode( LIGHTSMODE_ATTRACT ); m_stEdit.Set( StepsType_Invalid ); m_pEditSourceSteps.Set(nullptr); m_stEditSource.Set( StepsType_Invalid ); m_iEditCourseEntryIndex.Set( -1 ); m_sEditLocalProfileID.Set( "" ); m_bBackedOutOfFinalStage = false; m_bEarnedExtraStage = false; sExpandedSectionName = ""; ApplyCmdline(); } void GameState::JoinPlayer( PlayerNumber pn ) { // Make sure the join will be successful before doing it. -Kyz { int players_joined= 0; for(int i= 0; i < NUM_PLAYERS; ++i) { players_joined+= m_bSideIsJoined[i]; } if(players_joined > 0) { const Style* cur_style= GetCurrentStyle(PLAYER_INVALID); if(cur_style) { const Style* new_style= GAMEMAN->GetFirstCompatibleStyle(m_pCurGame, players_joined + 1, cur_style->m_StepsType); if(new_style == nullptr) { return; } } } } /* If joint premium and we're not taking away a credit for the 2nd join, * give the new player the same number of stage tokens that the old player * has. */ if( GetCoinMode() == CoinMode_Pay && GetPremium() == Premium_2PlayersFor1Credit && GetNumSidesJoined() == 1 ) m_iPlayerStageTokens[pn] = m_iPlayerStageTokens[this->GetMasterPlayerNumber()]; else m_iPlayerStageTokens[pn] = PREFSMAN->m_iSongsPerPlay; m_bSideIsJoined[pn] = true; if( this->GetMasterPlayerNumber() == PLAYER_INVALID ) this->SetMasterPlayerNumber(pn); // if first player to join, set start time if( GetNumSidesJoined() == 1 ) BeginGame(); // Count each player join as a play. { Profile* pMachineProfile = PROFILEMAN->GetMachineProfile(); pMachineProfile->m_iTotalSessions++; } // Set the current style to something appropriate for the new number of joined players. // beat gametype's versus styles use a different stepstype from its single // styles, so when GameCommand tries to join both players for a versus // style, it hits the assert when joining the first player. So if the first // player is being joined and the current styletype is for two players, // assume that the second player will be joined immediately afterwards and // don't try to change the style. -Kyz const Style* cur_style= GetCurrentStyle(PLAYER_INVALID); if(cur_style != nullptr && !(pn == PLAYER_1 && (cur_style->m_StyleType == StyleType_TwoPlayersTwoSides || cur_style->m_StyleType == StyleType_TwoPlayersSharedSides))) { const Style *pStyle; // Only use one player for StyleType_OnePlayerTwoSides and StepsTypes // that can only be played by one player (e.g. dance-solo, // dance-threepanel, popn-nine). -aj // XXX?: still shows joined player as "Insert Card". May not be an issue? -aj if( cur_style->m_StyleType == StyleType_OnePlayerTwoSides || cur_style->m_StepsType == StepsType_dance_solo || cur_style->m_StepsType == StepsType_dance_threepanel || cur_style->m_StepsType == StepsType_popn_nine ) pStyle = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, 1, cur_style->m_StepsType ); else pStyle = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, GetNumSidesJoined(), cur_style->m_StepsType ); // use SetCurrentStyle in case of StyleType_OnePlayerTwoSides SetCurrentStyle( pStyle, pn ); } Message msg( MessageIDToString(Message_PlayerJoined) ); msg.SetParam( "Player", pn ); MESSAGEMAN->Broadcast( msg ); } void GameState::UnjoinPlayer( PlayerNumber pn ) { /* Unjoin STATSMAN first, so steps used by this player are released * and can be released by PROFILEMAN. */ STATSMAN->UnjoinPlayer( pn ); m_bSideIsJoined[pn] = false; m_iPlayerStageTokens[pn] = 0; ResetPlayer( pn ); if( this->GetMasterPlayerNumber() == pn ) { // We can't use GetFirstHumanPlayer() because if both players were joined, GetFirstHumanPlayer() will always return PLAYER_1, even when PLAYER_1 is the player we're unjoining. FOREACH_HumanPlayer( hp ) { if( pn != hp ) { this->SetMasterPlayerNumber(hp); } } if( this->GetMasterPlayerNumber() == pn ) { this->SetMasterPlayerNumber(PLAYER_INVALID); } } Message msg( MessageIDToString(Message_PlayerUnjoined) ); msg.SetParam( "Player", pn ); MESSAGEMAN->Broadcast( msg ); // If there are no players left, reset some non-player-specific stuff, too. if( this->GetMasterPlayerNumber() == PLAYER_INVALID ) { SongOptions so; GetDefaultSongOptions( so ); m_SongOptions.Assign( ModsLevel_Preferred, so ); m_bDidModeChangeNoteSkin = false; } } /* xxx: handle multiplayer join? -aj */ namespace { bool JoinInputInternal( PlayerNumber pn ) { if( !GAMESTATE->PlayersCanJoin() ) return false; // If this side is already in, don't re-join. if( GAMESTATE->m_bSideIsJoined[pn] ) return false; // subtract coins int iCoinsNeededToJoin = GAMESTATE->GetCoinsNeededToJoin(); if( GAMESTATE->m_iCoins < iCoinsNeededToJoin ) return false; // not enough coins GAMESTATE->m_iCoins.Set( GAMESTATE->m_iCoins - iCoinsNeededToJoin ); GAMESTATE->JoinPlayer( pn ); // On Join, make sure to update Coins File BOOKKEEPER->WriteCoinsFile(GAMESTATE->m_iCoins.Get()); return true; } }; // Handle an input that can join a player. Return true if the player joined. bool GameState::JoinInput( PlayerNumber pn ) { // When AutoJoin is enabled, join all players on a single start press. if( GAMESTATE->m_bAutoJoin.Get() ) return JoinPlayers(); else return JoinInputInternal( pn ); } // Attempt to join all players, as if each player pressed Start. bool GameState::JoinPlayers() { bool bJoined = false; FOREACH_PlayerNumber( pn ) { if( JoinInputInternal(pn) ) bJoined = true; } return bJoined; } int GameState::GetCoinsNeededToJoin() const { int iCoinsToCharge = 0; if( GetCoinMode() == CoinMode_Pay ) iCoinsToCharge = PREFSMAN->m_iCoinsPerCredit; // If joint premium, don't take away a credit for the second join. if( GetPremium() == Premium_2PlayersFor1Credit && GetNumSidesJoined() == 1 ) iCoinsToCharge = 0; return iCoinsToCharge; } /* Game flow: * * BeginGame() - the first player has joined; the game is starting. * * PlayersFinalized() - player memory cards are loaded; later joins won't have memory cards this stage * * BeginStage() - gameplay is beginning * * optional: CancelStage() - gameplay aborted (Back pressed), undo BeginStage and back up * * CommitStageStats() - gameplay is finished * Saves STATSMAN->m_CurStageStats to the profiles, so profile information * is up-to-date for Evaluation. * * FinishStage() - gameplay and evaluation is finished * Clears data which was stored by CommitStageStats. */ void GameState::BeginGame() { m_timeGameStarted.Touch(); m_vpsNamesThatWereFilled.clear(); m_sPlayersThatWereFilled.clear(); // Play attract on the ending screen, then on the ranking screen // even if attract sounds are set to off. m_iNumTimesThroughAttract = -1; FOREACH_PlayerNumber( pn ) MEMCARDMAN->UnlockCard( pn ); } void GameState::LoadProfiles( bool bLoadEdits ) { // Unlock any cards that we might want to load. FOREACH_HumanPlayer( pn ) if( !PROFILEMAN->IsPersistentProfile(pn) ) MEMCARDMAN->UnlockCard( pn ); MEMCARDMAN->WaitForCheckingToComplete(); FOREACH_HumanPlayer( pn ) { // If a profile is already loaded, this was already called. if( PROFILEMAN->IsPersistentProfile(pn) ) continue; MEMCARDMAN->MountCard( pn ); bool bSuccess = PROFILEMAN->LoadFirstAvailableProfile( pn, bLoadEdits ); // load full profile MEMCARDMAN->UnmountCard( pn ); if( !bSuccess ) continue; // Lock the card on successful load, so we won't allow it to be changed. MEMCARDMAN->LockCard( pn ); LoadCurrentSettingsFromProfile( pn ); Profile* pPlayerProfile = PROFILEMAN->GetProfile( pn ); if( pPlayerProfile ) pPlayerProfile->m_iTotalSessions++; } } void GameState::SavePlayerProfiles() { FOREACH_HumanPlayer( pn ) SavePlayerProfile( pn ); } void GameState::SavePlayerProfile( PlayerNumber pn ) { if( !PROFILEMAN->IsPersistentProfile(pn) ) return; // AutoplayCPU should not save scores. -aj // xxx: this MAY cause issues with Multiplayer. However, without a working // Multiplayer build, we'll never know. -aj if( m_pPlayerState[pn]->m_PlayerController != PC_HUMAN ) return; bool bWasMemoryCard = PROFILEMAN->ProfileWasLoadedFromMemoryCard(pn); if( bWasMemoryCard ) MEMCARDMAN->MountCard( pn ); PROFILEMAN->SaveProfile( pn ); if( bWasMemoryCard ) MEMCARDMAN->UnmountCard( pn ); } bool GameState::HaveProfileToLoad() { FOREACH_HumanPlayer( pn ) { // We won't load this profile if it's already loaded. if( PROFILEMAN->IsPersistentProfile(pn) ) continue; // If a memory card is inserted, we'l try to load it. if( MEMCARDMAN->CardInserted(pn) ) return true; if( !PROFILEMAN->m_sDefaultLocalProfileID[pn].Get().empty() ) return true; } return false; } bool GameState::HaveProfileToSave() { FOREACH_HumanPlayer( pn ) if( PROFILEMAN->IsPersistentProfile(pn) ) return true; return false; } void GameState::SaveLocalData() { BOOKKEEPER->WriteToDisk(); PROFILEMAN->SaveMachineProfile(); } int GameState::GetNumStagesMultiplierForSong( const Song* pSong ) { int iNumStages = 1; ASSERT( pSong != nullptr ); if( pSong->IsMarathon() ) iNumStages *= 3; if( pSong->IsLong() ) iNumStages *= 2; return iNumStages; } int GameState::GetNumStagesForCurrentSongAndStepsOrCourse() const { int iNumStagesOfThisSong = 1; if( m_pCurSong ) { /* Extra stages need to only count as one stage in case a multi-stage * song is chosen. */ if( IsAnExtraStage() ) iNumStagesOfThisSong = 1; else iNumStagesOfThisSong = GameState::GetNumStagesMultiplierForSong( m_pCurSong ); } else if( m_pCurCourse ) iNumStagesOfThisSong = PREFSMAN->m_iSongsPerPlay; else return -1; iNumStagesOfThisSong = std::max( iNumStagesOfThisSong, 1 ); return iNumStagesOfThisSong; } // Called by ScreenGameplay. Set the length of the current song. void GameState::BeginStage() { if( m_bDemonstrationOrJukebox ) return; // This should only be called once per stage. if( m_iNumStagesOfThisSong != 0 ) LOG->Warn( "XXX: m_iNumStagesOfThisSong == %i?", m_iNumStagesOfThisSong ); ResetStageStatistics(); AdjustSync::ResetOriginalSyncData(); if( !ARE_STAGE_PLAYER_MODS_FORCED ) { FOREACH_PlayerNumber( p ) { ModsGroup &po = m_pPlayerState[p]->m_PlayerOptions; po.Assign(ModsLevel_Stage, m_pPlayerState[p]->m_PlayerOptions.GetPreferred()); } } if( !ARE_STAGE_SONG_MODS_FORCED ) m_SongOptions.Assign( ModsLevel_Stage, m_SongOptions.GetPreferred() ); STATSMAN->m_CurStageStats.m_fMusicRate = m_SongOptions.GetSong().m_fMusicRate; m_iNumStagesOfThisSong = GetNumStagesForCurrentSongAndStepsOrCourse(); ASSERT( m_iNumStagesOfThisSong != -1 ); FOREACH_EnabledPlayer( p ) { // only do this check with human players, assume CPU players (Rave) // always have tokens. -aj (this could probably be moved below, even.) if( !IsEventMode() && !IsCpuPlayer(p) ) { if(m_iPlayerStageTokens[p] < m_iNumStagesOfThisSong) { LuaHelpers::ReportScriptErrorFmt("Player %d only has %d stage tokens, but needs %d.", p, m_iPlayerStageTokens[p], m_iNumStagesOfThisSong); } } m_iPlayerStageTokens[p] -= m_iNumStagesOfThisSong; } FOREACH_HumanPlayer( pn ) if( CurrentOptionsDisqualifyPlayer(pn) ) STATSMAN->m_CurStageStats.m_player[pn].m_bDisqualified = true; m_bEarnedExtraStage = false; m_sStageGUID = CryptManager::GenerateRandomUUID(); } void GameState::CancelStage() { FOREACH_CpuPlayer( p ) { switch( m_PlayMode ) { case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: m_iPlayerStageTokens[p] = PREFSMAN->m_iSongsPerPlay; default: break; } } FOREACH_EnabledPlayer( p ) m_iPlayerStageTokens[p] += m_iNumStagesOfThisSong; m_iNumStagesOfThisSong = 0; ResetStageStatistics(); } void GameState::CommitStageStats() { if( m_bDemonstrationOrJukebox ) return; STATSMAN->CommitStatsToProfiles( &STATSMAN->m_CurStageStats ); // Update TotalPlaySeconds. int iPlaySeconds = std::max( 0, (int) m_timeGameStarted.GetDeltaTime() ); Profile* pMachineProfile = PROFILEMAN->GetMachineProfile(); pMachineProfile->m_iTotalSessionSeconds += iPlaySeconds; FOREACH_HumanPlayer( p ) { Profile* pPlayerProfile = PROFILEMAN->GetProfile( p ); if( pPlayerProfile ) pPlayerProfile->m_iTotalSessionSeconds += iPlaySeconds; } } /* Called by ScreenSelectMusic (etc). Increment the stage counter if we just * played a song. Might be called more than once. */ void GameState::FinishStage() { // Increment the stage counter. const int iOldStageIndex = m_iCurrentStageIndex; ++m_iCurrentStageIndex; m_iNumStagesOfThisSong = 0; EarnedExtraStage e = CalculateEarnedExtraStage(); STATSMAN->m_CurStageStats.m_EarnedExtraStage = e; if( e != EarnedExtraStage_No ) { LOG->Trace( "awarded extra stage" ); FOREACH_HumanPlayer( p ) { // todo: unhardcode the extra stage limit? -aj if( m_iAwardedExtraStages[p] < 2 ) { ++m_iAwardedExtraStages[p]; ++m_iPlayerStageTokens[p]; m_bEarnedExtraStage = true; } } } // Save the current combo to the profiles so it can be used for ComboContinuesBetweenSongs. FOREACH_HumanPlayer( p ) { Profile* pProfile = PROFILEMAN->GetProfile(p); pProfile->m_iCurrentCombo = STATSMAN->m_CurStageStats.m_player[p].m_iCurCombo; //If the sort order is Recent, move the profile to the top of the list. if (PREFSMAN->m_ProfileSortOrder == ProfileSortOrder_Recent && PROFILEMAN->IsPersistentProfile(p)) { int numLocalProfiles = PROFILEMAN->GetNumLocalProfiles(); for (int i = 0; i < numLocalProfiles; i++) { Profile *profile = PROFILEMAN->GetLocalProfileFromIndex(i); if (profile->m_sGuid == pProfile->m_sGuid) { PROFILEMAN->MoveProfileTopBottom(i, PREFSMAN->m_bProfileSortOrderAscending); break; } } } } if( m_bDemonstrationOrJukebox ) return; // todo: simplify. profile saving is accomplished in ScreenProfileSave // now; all this code does differently is save machine profile as well. -aj if( IsEventMode() ) { const int iSaveProfileEvery = 3; if( iOldStageIndex/iSaveProfileEvery < m_iCurrentStageIndex/iSaveProfileEvery ) { LOG->Trace( "Played %i stages; saving profiles ...", iSaveProfileEvery ); PROFILEMAN->SaveMachineProfile(); this->SavePlayerProfiles(); } } } void GameState::LoadCurrentSettingsFromProfile( PlayerNumber pn ) { if( !PROFILEMAN->IsPersistentProfile(pn) ) return; const Profile *pProfile = PROFILEMAN->GetProfile(pn); // apply saved default modifiers if any RString sModifiers; if( pProfile->GetDefaultModifiers( m_pCurGame, sModifiers ) ) { /* We don't save negative preferences (eg. "no reverse"). If the theme * sets a default of "reverse", and the player turns it off, we should * set it off. However, don't reset modifiers that aren't saved by the * profile, so we don't ignore unsaved modifiers when a profile is in use. */ PO_GROUP_CALL( m_pPlayerState[pn]->m_PlayerOptions, ModsLevel_Preferred, ResetSavedPrefs ); ApplyPreferredModifiers( pn, sModifiers ); } // Only set the sort order if it wasn't already set by a GameCommand (or by an earlier profile) if( m_PreferredSortOrder == SortOrder_Invalid && pProfile->m_SortOrder != SortOrder_Invalid ) m_PreferredSortOrder = pProfile->m_SortOrder; if( pProfile->m_LastDifficulty != Difficulty_Invalid ) m_PreferredDifficulty[pn].Set( pProfile->m_LastDifficulty ); if( pProfile->m_LastCourseDifficulty != Difficulty_Invalid ) m_PreferredCourseDifficulty[pn].Set( pProfile->m_LastCourseDifficulty ); // Only set the PreferredStepsType if it wasn't already set by a GameCommand (or by an earlier profile) if( m_PreferredStepsType == StepsType_Invalid && pProfile->m_LastStepsType != StepsType_Invalid ) m_PreferredStepsType.Set( pProfile->m_LastStepsType ); if( m_pPreferredSong == nullptr ) m_pPreferredSong = pProfile->m_lastSong.ToSong(); if( m_pPreferredCourse == nullptr ) m_pPreferredCourse = pProfile->m_lastCourse.ToCourse(); } void GameState::SaveCurrentSettingsToProfile( PlayerNumber pn ) { if( !PROFILEMAN->IsPersistentProfile(pn) ) return; if( m_bDemonstrationOrJukebox ) return; Profile* pProfile = PROFILEMAN->GetProfile(pn); pProfile->SetDefaultModifiers( m_pCurGame, m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().GetSavedPrefsString() ); if( IsSongSort(m_PreferredSortOrder) ) pProfile->m_SortOrder = m_PreferredSortOrder; if( m_PreferredDifficulty[pn] != Difficulty_Invalid ) pProfile->m_LastDifficulty = m_PreferredDifficulty[pn]; if( m_PreferredCourseDifficulty[pn] != Difficulty_Invalid ) pProfile->m_LastCourseDifficulty = m_PreferredCourseDifficulty[pn]; if( m_PreferredStepsType != StepsType_Invalid ) pProfile->m_LastStepsType = m_PreferredStepsType; if( m_pPreferredSong ) pProfile->m_lastSong.FromSong( m_pPreferredSong ); if( m_pPreferredCourse ) pProfile->m_lastCourse.FromCourse( m_pPreferredCourse ); } bool GameState::CanSafelyEnterGameplay(RString& reason) { if(!IsCourseMode()) { Song const* song= m_pCurSong; if(song == nullptr) { reason= "Current song is null."; return false; } } else { Course const* song= m_pCurCourse; if(song == nullptr) { reason= "Current course is null."; return false; } } FOREACH_EnabledPlayer(pn) { Style const* style= GetCurrentStyle(pn); if(style == nullptr) { reason= ssprintf("Style for player %d is null.", pn+1); return false; } if(!IsCourseMode()) { Steps const* steps= m_pCurSteps[pn]; if(steps == nullptr) { reason= ssprintf("Steps for player %d is null.", pn+1); return false; } if(steps->m_StepsType != style->m_StepsType) { reason= ssprintf("Player %d StepsType %s for steps does not equal " "StepsType %s for style.", pn+1, GAMEMAN->GetStepsTypeInfo(steps->m_StepsType).szName, GAMEMAN->GetStepsTypeInfo(style->m_StepsType).szName); return false; } if(steps->m_pSong != m_pCurSong) { reason= ssprintf("Steps for player %d are not for the current song.", pn+1); return false; } NoteData ndtemp; steps->GetNoteData(ndtemp); if(ndtemp.GetNumTracks() != style->m_iColsPerPlayer) { reason= ssprintf("Steps for player %d have %d columns, style has %d " "columns.", pn+1, ndtemp.GetNumTracks(), style->m_iColsPerPlayer); return false; } } else { Trail const* steps= m_pCurTrail[pn]; if(steps == nullptr) { reason= ssprintf("Steps for player %d is null.", pn+1); return false; } if(steps->m_StepsType != style->m_StepsType) { reason= ssprintf("Player %d StepsType %s for steps does not equal " "StepsType %s for style.", pn+1, GAMEMAN->GetStepsTypeInfo(steps->m_StepsType).szName, GAMEMAN->GetStepsTypeInfo(style->m_StepsType).szName); return false; } } } return true; } void GameState::SetCompatibleStylesForPlayers() { bool style_set= false; if(IsCourseMode()) { if(m_pCurCourse != nullptr) { const Style* style= m_pCurCourse->GetCourseStyle(m_pCurGame, GetNumSidesJoined()); if(style != nullptr) { style_set= true; SetCurrentStyle(style, PLAYER_INVALID); } } else if(GetCurrentStyle(PLAYER_INVALID) == nullptr) { std::vector vst; GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst); const Style *style = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, GetNumSidesJoined(), vst[0]); SetCurrentStyle(style, PLAYER_INVALID); } } if(!style_set) { FOREACH_EnabledPlayer(pn) { StepsType st= StepsType_Invalid; if(m_pCurSteps[pn] != nullptr) { st= m_pCurSteps[pn]->m_StepsType; } else if(m_pCurTrail[pn] != nullptr) { st= m_pCurTrail[pn]->m_StepsType; } else { std::vector vst; GAMEMAN->GetStepsTypesForGame(m_pCurGame, vst); st= vst[0]; } const Style *style = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, GetNumSidesJoined(), st); SetCurrentStyle(style, pn); } } } void GameState::ForceSharedSidesMatch() { PlayerNumber pn_with_shared= PLAYER_INVALID; const Style* shared_style= nullptr; FOREACH_EnabledPlayer(pn) { const Style* style= GetCurrentStyle(pn); ASSERT_M(style != nullptr, "Style being null should not be possible."); if(style->m_StyleType == StyleType_TwoPlayersSharedSides) { pn_with_shared= pn; shared_style= style; } } if(pn_with_shared != PLAYER_INVALID) { ASSERT_M(GetNumPlayersEnabled() == 2, "2 players must be enabled for shared sides."); PlayerNumber other_pn= OPPOSITE_PLAYER[pn_with_shared]; const Style* other_style= GetCurrentStyle(other_pn); ASSERT_M(other_style != nullptr, "Other player's style being null should not be possible."); if(other_style->m_StyleType != StyleType_TwoPlayersSharedSides) { SetCurrentStyle(shared_style, other_pn); if(IsCourseMode()) { m_pCurTrail[other_pn].Set(m_pCurTrail[pn_with_shared]); } else { m_pCurSteps[other_pn].Set(m_pCurSteps[pn_with_shared]); } } } } void GameState::ForceOtherPlayersToCompatibleSteps(PlayerNumber main) { if(IsCourseMode()) { Trail* steps_to_match= m_pCurTrail[main].Get(); if(steps_to_match == nullptr) { return; } int num_players= GAMESTATE->GetNumPlayersEnabled(); StyleType styletype_to_match= GAMEMAN->GetFirstCompatibleStyle( GAMESTATE->GetCurrentGame(), num_players, steps_to_match->m_StepsType) ->m_StyleType; FOREACH_EnabledPlayer(pn) { Trail* pn_steps= m_pCurTrail[pn].Get(); bool match_failed= pn_steps == nullptr; if(steps_to_match != pn_steps && pn_steps != nullptr) { StyleType pn_styletype= GAMEMAN->GetFirstCompatibleStyle( GAMESTATE->GetCurrentGame(), num_players, pn_steps->m_StepsType) ->m_StyleType; if(styletype_to_match == StyleType_TwoPlayersSharedSides || pn_styletype == StyleType_TwoPlayersSharedSides) { match_failed= true; } } if(match_failed) { m_pCurTrail[pn].Set(steps_to_match); } } } else { Steps* steps_to_match= m_pCurSteps[main].Get(); if(steps_to_match == nullptr) { return; } int num_players= GAMESTATE->GetNumPlayersEnabled(); StyleType styletype_to_match= GAMEMAN->GetFirstCompatibleStyle( GAMESTATE->GetCurrentGame(), num_players, steps_to_match->m_StepsType) ->m_StyleType; RString music_to_match= steps_to_match->GetMusicFile(); FOREACH_EnabledPlayer(pn) { Steps* pn_steps= m_pCurSteps[pn].Get(); bool match_failed= pn_steps == nullptr; if(steps_to_match != pn_steps && pn_steps != nullptr) { StyleType pn_styletype= GAMEMAN->GetFirstCompatibleStyle( GAMESTATE->GetCurrentGame(), num_players, pn_steps->m_StepsType) ->m_StyleType; if(styletype_to_match == StyleType_TwoPlayersSharedSides || pn_styletype == StyleType_TwoPlayersSharedSides) { match_failed= true; } if(music_to_match != pn_steps->GetMusicFile()) { match_failed= true; } } if(match_failed) { m_pCurSteps[pn].Set(steps_to_match); } } } } void GameState::Update( float fDelta ) { m_SongOptions.Update( fDelta ); FOREACH_PlayerNumber( p ) { m_pPlayerState[p]->Update( fDelta ); if( !m_bGoalComplete[p] && IsGoalComplete(p) ) { m_bGoalComplete[p] = true; MESSAGEMAN->Broadcast( (MessageID)(Message_GoalCompleteP1+Enum::to_integral(p)) ); } } if( GAMESTATE->m_pCurCourse ) { if( GAMESTATE->m_pCurCourse->m_fGoalSeconds > 0 && !m_bWorkoutGoalComplete ) { const StageStats &ssCurrent = STATSMAN->m_CurStageStats; bool bGoalComplete = ssCurrent.m_fGameplaySeconds > GAMESTATE->m_pCurCourse->m_fGoalSeconds; if( bGoalComplete ) { MESSAGEMAN->Broadcast( "WorkoutGoalComplete" ); m_bWorkoutGoalComplete = true; } } } } void GameState::SetCurGame( const Game *pGame ) { m_pCurGame.Set( pGame ); RString sGame = pGame ? RString(pGame->m_szName) : RString(); PREFSMAN->SetCurrentGame( sGame ); } const float GameState::MUSIC_SECONDS_INVALID = -5000.0f; void GameState::ResetMusicStatistics() { m_Position.Reset(); m_LastPositionTimer.Touch(); m_LastPositionSeconds = 0.0f; Actor::SetBGMTime( 0, 0, 0, 0 ); FOREACH_PlayerNumber( p ) { m_pPlayerState[p]->m_Position.Reset(); } } void GameState::ResetStageStatistics() { StageStats OldStats = STATSMAN->m_CurStageStats; STATSMAN->m_CurStageStats = StageStats(); if( PREFSMAN->m_bComboContinuesBetweenSongs ) { FOREACH_PlayerNumber( p ) { bool FirstSong = m_iCurrentStageIndex == 0; if( FirstSong ) { Profile* pProfile = PROFILEMAN->GetProfile(p); STATSMAN->m_CurStageStats.m_player[p].m_iCurCombo = pProfile->m_iCurrentCombo; } else { STATSMAN->m_CurStageStats.m_player[p].m_iCurCombo = OldStats.m_player[p].m_iCurCombo; } } } RemoveAllActiveAttacks(); FOREACH_PlayerNumber( p ) m_pPlayerState[p]->RemoveAllInventory(); m_fOpponentHealthPercent = 1; m_fHasteRate = 0; m_fLastHasteUpdateMusicSeconds = 0; m_fAccumulatedHasteSeconds = 0; m_fTugLifePercentP1 = 0.5f; FOREACH_PlayerNumber( p ) { m_pPlayerState[p]->m_fSuperMeter = 0; m_pPlayerState[p]->m_HealthState = HealthState_Alive; m_pPlayerState[p]->m_iLastPositiveSumOfAttackLevels = 0; m_pPlayerState[p]->m_fSecondsUntilAttacksPhasedOut = 0; // PlayerAI not affected m_bGoalComplete[p] = false; } m_bWorkoutGoalComplete = false; FOREACH_PlayerNumber( p ) { m_vLastStageAwards[p].clear(); m_vLastPeakComboAwards[p].clear(); } // Reset the round seed. Do this here and not in FinishStage so that players // get new shuffle patterns if they Back out of gameplay and play again. m_iStageSeed = rand(); } void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp ) { /* It's not uncommon to get a lot of duplicated positions from the sound * driver, like so: 13.120953,13.130975,13.130975,13.130975,13.140998,... * This causes visual stuttering of the arrows. To compensate, keep a * RageTimer since the last change. */ if(fPositionSeconds == m_LastPositionSeconds && !m_paused) fPositionSeconds += m_LastPositionTimer.Ago(); else { //LOG->Info("Time difference: %+f", // m_LastPositionTimer.Ago() - (fPositionSeconds - m_LastPositionSeconds) //); m_LastPositionTimer.Touch(); m_LastPositionSeconds = fPositionSeconds; } m_Position.UpdateSongPosition( fPositionSeconds, timing, timestamp ); FOREACH_EnabledPlayer( pn ) { if( m_pCurSteps[pn] ) { float fAdditionalVisualDelay = m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().m_fVisualDelay; m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, *m_pCurSteps[pn]->GetTimingData(), timestamp, fAdditionalVisualDelay ); Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset ); } } Actor::SetBGMTime( GAMESTATE->m_Position.m_fMusicSecondsVisible, GAMESTATE->m_Position.m_fSongBeatVisible, fPositionSeconds, GAMESTATE->m_Position.m_fSongBeatNoOffset ); // LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze ); } /* update player position code goes here */ float GameState::GetSongPercent( float beat ) const { // 0 = first step; 1 = last step float curTime = this->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat(beat); return (curTime - m_pCurSong->GetFirstSecond()) / m_pCurSong->GetLastSecond(); } int GameState::GetNumStagesLeft( PlayerNumber pn ) const { return m_iPlayerStageTokens[pn]; } int GameState::GetSmallestNumStagesLeftForAnyHumanPlayer() const { if( IsEventMode() ) return 999; int iSmallest = INT_MAX; FOREACH_HumanPlayer( p ) iSmallest = std::min( iSmallest, m_iPlayerStageTokens[p] ); return iSmallest; } bool GameState::IsFinalStageForAnyHumanPlayer() const { return GetSmallestNumStagesLeftForAnyHumanPlayer() == 1; } bool GameState::IsFinalStageForEveryHumanPlayer() const { int song_cost= 1; if(m_pCurSong != nullptr) { if(m_pCurSong->IsLong()) { song_cost= 2; } else if(m_pCurSong->IsMarathon()) { song_cost= 3; } } // If we're on gameplay or evaluation, they set this to false because those // screens have already had the stage tokens subtracted. song_cost*= m_AdjustTokensBySongCostForFinalStageCheck; int num_on_final= 0; int num_humans= 0; FOREACH_HumanPlayer(p) { if(m_iPlayerStageTokens[p] - song_cost <= 0) { ++num_on_final; } ++num_humans; } return num_on_final >= num_humans; } bool GameState::IsAnExtraStage() const { if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid ) return false; return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] > 0; } static ThemeMetric LOCK_EXTRA_STAGE_SELECTION("GameState","LockExtraStageSelection"); bool GameState::IsAnExtraStageAndSelectionLocked() const { return IsAnExtraStage() && LOCK_EXTRA_STAGE_SELECTION; } bool GameState::IsExtraStage() const { if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid ) return false; return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 1; } bool GameState::IsExtraStage2() const { if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid ) return false; return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 2; } Stage GameState::GetCurrentStage() const { if( m_bDemonstrationOrJukebox ) return Stage_Demo; // "event" has precedence else if( IsEventMode() ) return Stage_Event; else if( m_PlayMode == PLAY_MODE_ONI ) return Stage_Oni; else if( m_PlayMode == PLAY_MODE_NONSTOP ) return Stage_Nonstop; else if( m_PlayMode == PLAY_MODE_ENDLESS ) return Stage_Endless; else if( IsExtraStage() ) return Stage_Extra1; else if( IsExtraStage2() ) return Stage_Extra2; // Previous logic did not factor in current song length, or the fact that // players aren't allowed to start a song with 0 tokens. This new // function also has logic for handling the Gameplay and Evaluation cases // which used to require workarounds on the theme side. -Kyz else if(IsFinalStageForEveryHumanPlayer()) return Stage_Final; else { switch( this->m_iCurrentStageIndex ) { case 0: return Stage_1st; case 1: return Stage_2nd; case 2: return Stage_3rd; case 3: return Stage_4th; case 4: return Stage_5th; case 5: return Stage_6th; default: return Stage_Next; } } } int GameState::GetCourseSongIndex() const { // iSongsPlayed includes the current song, so it's 1-based; subtract one. if( GAMESTATE->m_bMultiplayer ) { FOREACH_EnabledMultiPlayer(mp) return STATSMAN->m_CurStageStats.m_multiPlayer[mp].m_iSongsPlayed-1; FAIL_M("At least one MultiPlayer must be joined."); } else { return STATSMAN->m_CurStageStats.m_player[this->GetMasterPlayerNumber()].m_iSongsPlayed-1; } } /* Hack: when we're loading a new course song, we want to display the new song * number, even though we haven't started that song yet. */ int GameState::GetLoadingCourseSongIndex() const { int iIndex = GetCourseSongIndex(); if( m_bLoadingNextSong ) ++iIndex; return iIndex; } static char const* prepare_song_failures[]= { "success", "no_current_song", "card_mount_failed", "load_interrupted", }; int GameState::prepare_song_for_gameplay() { Song* curr= m_pCurSong; if(curr == nullptr) { return 1; } if(curr->m_LoadedFromProfile == ProfileSlot_Invalid) { return 0; } ProfileSlot prof_slot= curr->m_LoadedFromProfile; PlayerNumber slot_as_pn= PlayerNumber(prof_slot); if(!PROFILEMAN->ProfileWasLoadedFromMemoryCard(slot_as_pn)) { return 0; } if(!MEMCARDMAN->MountCard(slot_as_pn)) { return 2; } RString prof_dir= PROFILEMAN->GetProfileDir(prof_slot); // Song loading changes its paths to point to the cache area. -Kyz RString to_dir= curr->GetSongDir(); RString from_dir= curr->GetPreCustomifyDir(); // The problem of what files to copy is complicated by steps being able to // specify their own music file, and the variety of step file formats. // Complex logic to figure out what files the song actually uses would be // bug prone. Just copy all audio files and step files. -Kyz std::vector copy_exts= ActorUtil::GetTypeExtensionList(FT_Sound); copy_exts.push_back("sm"); copy_exts.push_back("ssc"); copy_exts.push_back("lrc"); std::vector files_in_dir; FILEMAN->GetDirListingWithMultipleExtensions(from_dir, copy_exts, files_in_dir); for(size_t i= 0; i < files_in_dir.size(); ++i) { RString& fname= files_in_dir[i]; if(!FileCopy(from_dir + fname, to_dir + fname)) { return 3; } } MEMCARDMAN->UnmountCard(slot_as_pn); return 0; } static LocalizedString PLAYER1 ("GameState","Player 1"); static LocalizedString PLAYER2 ("GameState","Player 2"); static LocalizedString CPU ("GameState","CPU"); RString GameState::GetPlayerDisplayName( PlayerNumber pn ) const { ASSERT( IsPlayerEnabled(pn) ); const LocalizedString *pDefaultNames[] = { &PLAYER1, &PLAYER2 }; if( IsHumanPlayer(pn) ) { if( !PROFILEMAN->GetPlayerName(pn).empty() ) return PROFILEMAN->GetPlayerName(pn); else return pDefaultNames[pn]->GetValue(); } else { return CPU.GetValue(); } } bool GameState::PlayersCanJoin() const { if(GetNumSidesJoined() == 0) { return true; } // If we check the style and it comes up nullptr, either the style has not been // chosen, or we're on ScreenSelectMusic with AutoSetStyle. // If the style does not come up nullptr, we might be on a screen in a custom // theme that wants to allow joining after the style is set anyway. // Either way, we can't use the existence of a style to decide. // -Kyz if( ALLOW_LATE_JOIN.IsLoaded() && ALLOW_LATE_JOIN ) { Screen *pScreen = SCREENMAN->GetTopScreen(); if(pScreen) { if(!pScreen->AllowLateJoin()) { return false; } } // We can't use FOREACH_EnabledPlayer because that uses PlayersCanJoin // in part of its logic chain. -Kyz FOREACH_PlayerNumber(pn) { const Style* style= GetCurrentStyle(pn); if(style) { const Style* compat_style= GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, 2, style->m_StepsType); if(compat_style == nullptr) { return false; } } } return true; } return false; } int GameState::GetNumSidesJoined() const { int iNumSidesJoined = 0; FOREACH_PlayerNumber( p ) if( m_bSideIsJoined[p] ) iNumSidesJoined++; // left side, and right side return iNumSidesJoined; } const Game* GameState::GetCurrentGame() const { ASSERT( m_pCurGame != nullptr ); // the game must be set before calling this return m_pCurGame; } const Style* GameState::GetCurrentStyle(PlayerNumber pn) const { if(GetCurrentGame() == nullptr) { return nullptr; } if(!GetCurrentGame()->m_PlayersHaveSeparateStyles) { return m_pCurStyle; } else { if(pn >= NUM_PLAYERS) { return m_SeparatedStyles[PLAYER_1] == nullptr ? m_SeparatedStyles[PLAYER_2] : m_SeparatedStyles[PLAYER_1]; } return m_SeparatedStyles[pn]; } } void GameState::SetCurrentStyle(const Style *style, PlayerNumber pn) { if(!GetCurrentGame()->m_PlayersHaveSeparateStyles) { m_pCurStyle.Set(style); } else { if(pn == PLAYER_INVALID) { FOREACH_PlayerNumber(rpn) { m_SeparatedStyles[rpn]= style; } } else { m_SeparatedStyles[pn]= style; } } if(INPUTMAPPER) { if(GetCurrentStyle(pn) && GetCurrentStyle(pn)->m_StyleType == StyleType_OnePlayerTwoSides) { // If the other player is joined, unjoin them because this style only // allows one player. PlayerNumber other_pn= OPPOSITE_PLAYER[this->GetMasterPlayerNumber()]; if(GetNumSidesJoined() > 1) { UnjoinPlayer(other_pn); } INPUTMAPPER->SetJoinControllers(this->GetMasterPlayerNumber()); } else INPUTMAPPER->SetJoinControllers(PLAYER_INVALID); } } bool GameState::SetCompatibleStyle(StepsType stype, PlayerNumber pn) { bool style_incompatible= false; if(!GetCurrentStyle(pn)) { style_incompatible= true; } else { style_incompatible= stype != GetCurrentStyle(pn)->m_StepsType; } if(CommonMetrics::AUTO_SET_STYLE && style_incompatible) { const Style* compatible_style= GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, GetNumSidesJoined(), stype); if(!compatible_style) { return false; } SetCurrentStyle(compatible_style, pn); } return stype == GetCurrentStyle(pn)->m_StepsType; } bool GameState::IsPlayerEnabled( PlayerNumber pn ) const { // In rave, all players are present. Non-human players are CPU controlled. switch( m_PlayMode ) { case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: return true; default: return IsHumanPlayer(pn); } } bool GameState::IsMultiPlayerEnabled( MultiPlayer mp ) const { return m_MultiPlayerStatus[ mp ] == MultiPlayerStatus_Joined; } bool GameState::IsPlayerEnabled( const PlayerState* pPlayerState ) const { if( pPlayerState->m_mp != MultiPlayer_Invalid ) return IsMultiPlayerEnabled( pPlayerState->m_mp ); if( pPlayerState->m_PlayerNumber != PLAYER_INVALID ) return IsPlayerEnabled( pPlayerState->m_PlayerNumber ); return false; } int GameState::GetNumPlayersEnabled() const { int count = 0; FOREACH_EnabledPlayer( pn ) count++; return count; } bool GameState::IsHumanPlayer( PlayerNumber pn ) const { if( pn == PLAYER_INVALID ) return false; if(GetCurrentGame()->m_PlayersHaveSeparateStyles) { if( GetCurrentStyle(pn) == nullptr ) // no style chosen { return m_bSideIsJoined[pn]; } else { StyleType type = GetCurrentStyle(pn)->m_StyleType; switch( type ) { case StyleType_TwoPlayersTwoSides: case StyleType_TwoPlayersSharedSides: return true; case StyleType_OnePlayerOneSide: case StyleType_OnePlayerTwoSides: return pn == this->GetMasterPlayerNumber(); default: FAIL_M(ssprintf("Invalid style type: %i", type)); } } } if( GetCurrentStyle(pn) == nullptr ) // no style chosen { return m_bSideIsJoined[pn]; // only allow input from sides that have already joined } StyleType type = GetCurrentStyle(pn)->m_StyleType; switch( type ) { case StyleType_TwoPlayersTwoSides: case StyleType_TwoPlayersSharedSides: return true; case StyleType_OnePlayerOneSide: case StyleType_OnePlayerTwoSides: return pn == this->GetMasterPlayerNumber(); default: FAIL_M(ssprintf("Invalid style type: %i", type)); } } int GameState::GetNumHumanPlayers() const { int count = 0; FOREACH_HumanPlayer( pn ) count++; return count; } PlayerNumber GameState::GetFirstHumanPlayer() const { FOREACH_HumanPlayer( pn ) return pn; return PLAYER_INVALID; } PlayerNumber GameState::GetFirstDisabledPlayer() const { FOREACH_PlayerNumber( pn ) if( !IsPlayerEnabled(pn) ) return pn; return PLAYER_INVALID; } bool GameState::IsCpuPlayer( PlayerNumber pn ) const { return IsPlayerEnabled(pn) && !IsHumanPlayer(pn); } bool GameState::AnyPlayersAreCpu() const { FOREACH_CpuPlayer( pn ) return true; return false; } bool GameState::IsCourseMode() const { switch(m_PlayMode) { case PLAY_MODE_ONI: case PLAY_MODE_NONSTOP: case PLAY_MODE_ENDLESS: return true; default: return false; } } bool GameState::IsBattleMode() const { switch( m_PlayMode ) { case PLAY_MODE_BATTLE: return true; default: return false; } } EarnedExtraStage GameState::CalculateEarnedExtraStage() const { if( IsEventMode() ) return EarnedExtraStage_No; if( !PREFSMAN->m_bAllowExtraStage ) return EarnedExtraStage_No; if( m_PlayMode != PLAY_MODE_REGULAR ) return EarnedExtraStage_No; if( m_bBackedOutOfFinalStage ) return EarnedExtraStage_No; if( GetSmallestNumStagesLeftForAnyHumanPlayer() > 0 ) return EarnedExtraStage_No; if( m_iAwardedExtraStages[this->GetMasterPlayerNumber()] >= 2 ) return EarnedExtraStage_No; FOREACH_EnabledPlayer( pn ) { Difficulty dc = m_pCurSteps[pn]->GetDifficulty(); switch( dc ) { case Difficulty_Edit: if( !EDIT_ALLOWED_FOR_EXTRA ) continue; // can't use edit steps break; default: if( dc < MIN_DIFFICULTY_FOR_EXTRA ) continue; // not hard enough! break; } if( IsExtraStage() ) { if( ALLOW_EXTRA_2 && STATSMAN->m_CurStageStats.m_player[pn].GetGrade() <= GRADE_TIER_FOR_EXTRA_2 ) return EarnedExtraStage_Extra2; } else if( STATSMAN->m_CurStageStats.m_player[pn].GetGrade() <= GRADE_TIER_FOR_EXTRA_1 ) { return EarnedExtraStage_Extra1; } } return EarnedExtraStage_No; } PlayerNumber GameState::GetBestPlayer() const { FOREACH_PlayerNumber( pn ) if( GetStageResult(pn) == RESULT_WIN ) return pn; return PLAYER_INVALID; // draw } StageResult GameState::GetStageResult( PlayerNumber pn ) const { switch( m_PlayMode ) { case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: if( std::abs(m_fTugLifePercentP1 - 0.5f) < 0.0001f ) return RESULT_DRAW; switch( pn ) { case PLAYER_1: return (m_fTugLifePercentP1>=0.5f)?RESULT_WIN:RESULT_LOSE; case PLAYER_2: return (m_fTugLifePercentP1<0.5f)?RESULT_WIN:RESULT_LOSE; default: FAIL_M("Invalid player for battle! Aborting..."); } default: break; } StageResult win = RESULT_WIN; FOREACH_PlayerNumber( p ) { if( p == pn ) continue; // If anyone did just as well, at best it's a draw. if( STATSMAN->m_CurStageStats.m_player[p].m_iActualDancePoints == STATSMAN->m_CurStageStats.m_player[pn].m_iActualDancePoints ) win = RESULT_DRAW; // If anyone did better, we lost. if( STATSMAN->m_CurStageStats.m_player[p].m_iActualDancePoints > STATSMAN->m_CurStageStats.m_player[pn].m_iActualDancePoints ) return RESULT_LOSE; } return win; } void GameState::GetDefaultPlayerOptions( PlayerOptions &po ) { po.Init(); po.FromString( PREFSMAN->m_sDefaultModifiers ); po.FromString( CommonMetrics::DEFAULT_MODIFIERS ); if( po.m_sNoteSkin.empty() ) po.m_sNoteSkin = CommonMetrics::DEFAULT_NOTESKIN_NAME; } void GameState::GetDefaultSongOptions( SongOptions &so ) { so.Init(); so.FromString( PREFSMAN->m_sDefaultModifiers ); so.FromString( CommonMetrics::DEFAULT_MODIFIERS ); } void GameState::ResetToDefaultSongOptions( ModsLevel l ) { SongOptions so; GetDefaultSongOptions( so ); m_SongOptions.Assign( l, so ); } void GameState::ApplyPreferredModifiers( PlayerNumber pn, RString sModifiers ) { m_pPlayerState[pn]->m_PlayerOptions.FromString( ModsLevel_Preferred, sModifiers ); m_SongOptions.FromString( ModsLevel_Preferred, sModifiers ); } void GameState::ApplyStageModifiers( PlayerNumber pn, RString sModifiers ) { m_pPlayerState[pn]->m_PlayerOptions.FromString( ModsLevel_Stage, sModifiers ); m_SongOptions.FromString( ModsLevel_Stage, sModifiers ); } void GameState::ClearStageModifiersIllegalForCourse() { FOREACH_EnabledPlayer( pn ) PO_GROUP_CALL( m_pPlayerState[pn]->m_PlayerOptions, ModsLevel_Stage, ResetSavedPrefsInvalidForCourse ); } bool GameState::CurrentOptionsDisqualifyPlayer( PlayerNumber pn ) { if( !PREFSMAN->m_bDisqualification ) return false; if( !IsHumanPlayer(pn) ) return false; const PlayerOptions &po = m_pPlayerState[pn]->m_PlayerOptions.GetPreferred(); const SongOptions &so = m_SongOptions.GetPreferred(); // Playing a song/course at a slower music rate should disqualify score. -x0rbl if (so.m_fMusicRate < 1.0) { return true; } // Check the stored player options for disqualify. Don't disqualify because // of mods that were forced. if( IsCourseMode() ) return po.IsEasierForCourseAndTrail( m_pCurCourse, m_pCurTrail[pn] ); else return po.IsEasierForSongAndSteps( m_pCurSong, m_pCurSteps[pn], pn); } /* reset noteskins (?) * GameState::ResetNoteSkins() * GameState::ResetNoteSkinsForPlayer( PlayerNumber pn ) * */ void GameState::GetAllUsedNoteSkins( std::vector &out ) const { FOREACH_EnabledPlayer( pn ) { out.push_back( m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin ); // Add noteskins that are used in courses. if( IsCourseMode() ) { const Trail *pTrail = m_pCurTrail[pn]; ASSERT( pTrail != nullptr ); for (TrailEntry const &e : pTrail->m_vEntries) { PlayerOptions po; po.FromString( e.Modifiers ); if( !po.m_sNoteSkin.empty() ) out.push_back( po.m_sNoteSkin ); } } } // Remove duplicates. sort( out.begin(), out.end() ); out.erase( unique( out.begin(), out.end() ), out.end() ); } void GameState::RemoveAllActiveAttacks() // called on end of song { FOREACH_PlayerNumber( p ) m_pPlayerState[p]->RemoveActiveAttacks(); } void GameState::AddStageToPlayer( PlayerNumber pn ) { // Add one stage more to player (bonus) -cerbo ++m_iPlayerStageTokens[pn]; } template void setmin( T &a, const T &b ) { a = std::min(a, b); } template void setmax( T &a, const T &b ) { a = std::max(a, b); } FailType GameState::GetPlayerFailType( const PlayerState *pPlayerState ) const { PlayerNumber pn = pPlayerState->m_PlayerNumber; FailType ft = pPlayerState->m_PlayerOptions.GetCurrent().m_FailType; // If the player changed the fail mode explicitly, leave it alone. if( m_bFailTypeWasExplicitlySet ) return ft; if( IsCourseMode() ) { if( PREFSMAN->m_bMinimum1FullSongInCourses && GetCourseSongIndex()==0 ) ft = std::max( ft, FailType_ImmediateContinue ); // take the least harsh of the two FailTypes } else { Difficulty dc = Difficulty_Invalid; if( m_pCurSteps[pn] ) dc = m_pCurSteps[pn]->GetDifficulty(); bool bFirstStage = false; if( !IsEventMode() ) bFirstStage |= m_iPlayerStageTokens[pPlayerState->m_PlayerNumber] == PREFSMAN->m_iSongsPerPlay-1; // HACK; -1 because this is called during gameplay // Easy and beginner are never harder than FAIL_IMMEDIATE_CONTINUE. if( dc <= Difficulty_Easy ) setmax( ft, FailType_ImmediateContinue ); if( dc <= Difficulty_Easy && bFirstStage && PREFSMAN->m_bFailOffForFirstStageEasy ) setmax( ft, FailType_Off ); /* If beginner's steps were chosen, and this is the first stage, * turn off failure completely. */ if( dc == Difficulty_Beginner && bFirstStage ) setmax( ft, FailType_Off ); if( dc == Difficulty_Beginner && PREFSMAN->m_bFailOffInBeginner ) setmax( ft, FailType_Off ); } return ft; } bool GameState::ShowW1() const { AllowW1 pref = PREFSMAN->m_AllowW1; switch( pref ) { case ALLOW_W1_NEVER: return false; case ALLOW_W1_COURSES_ONLY: return IsCourseMode(); case ALLOW_W1_EVERYWHERE: return true; default: FAIL_M(ssprintf("Invalid AllowW1 preference: %i", pref)); } } static ThemeMetric PROFILE_RECORD_FEATS("GameState","ProfileRecordFeats"); static ThemeMetric CATEGORY_RECORD_FEATS("GameState","CategoryRecordFeats"); void GameState::GetRankingFeats( PlayerNumber pn, std::vector &asFeatsOut ) const { if( !IsHumanPlayer(pn) ) return; Profile *pProf = PROFILEMAN->GetProfile(pn); // Check for feats even if the PlayMode is rave or battle because the player // may have made high scores then switched modes. PlayMode mode = m_PlayMode.Get(); char const *modeStr = PlayModeToString(mode).c_str(); CHECKPOINT_M( ssprintf("Getting the feats for %s", modeStr)); switch( mode ) { case PLAY_MODE_REGULAR: case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: { StepsType st = GetCurrentStyle(pn)->m_StepsType; // Find unique Song and Steps combinations that were played. // We must keep only the unique combination or else we'll double-count // high score markers. std::vector vSongAndSteps; for( unsigned i=0; im_vPlayedStageStats.size(); i++ ) { CHECKPOINT_M( ssprintf("%u/%i", i, (int)STATSMAN->m_vPlayedStageStats.size() ) ); SongAndSteps sas; ASSERT( !STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs.empty() ); sas.pSong = STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs[0]; ASSERT( sas.pSong != nullptr ); if( STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps.empty() ) continue; sas.pSteps = STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps[0]; ASSERT( sas.pSteps != nullptr ); vSongAndSteps.push_back( sas ); } CHECKPOINT_M( ssprintf("All songs/steps from %s gathered", modeStr)); sort( vSongAndSteps.begin(), vSongAndSteps.end() ); std::vector::iterator toDelete = unique( vSongAndSteps.begin(), vSongAndSteps.end() ); vSongAndSteps.erase(toDelete, vSongAndSteps.end()); CHECKPOINT_M( "About to find records from the gathered."); for( unsigned i=0; iGetMachineProfile()->GetStepsHighScoreList(pSong,pSteps); for( unsigned j=0; jGetTranslitMainTitle().c_str(), DifficultyToString(pSteps->GetDifficulty()).c_str() ); feat.pStringToFill = hs.GetNameMutable(); feat.grade = hs.GetGrade(); feat.fPercentDP = hs.GetPercentDP(); feat.iScore = hs.GetScore(); if( pSong->HasBanner() ) feat.Banner = pSong->GetBannerPath(); asFeatsOut.push_back( feat ); } } // Find Personal Records if( pProf && PROFILE_RECORD_FEATS ) { HighScoreList &hsl = pProf->GetStepsHighScoreList(pSong,pSteps); for( unsigned j=0; jGetTranslitMainTitle().c_str(), DifficultyToString(pSteps->GetDifficulty()).c_str() ); feat.pStringToFill = hs.GetNameMutable(); feat.grade = hs.GetGrade(); feat.fPercentDP = hs.GetPercentDP(); feat.iScore = hs.GetScore(); // XXX: temporary hack // Why is this here? -aj if( pSong->HasBackground() ) feat.Banner = pSong->GetBackgroundPath(); // if( pSong->HasBanner() ) // feat.Banner = pSong->GetBannerPath(); asFeatsOut.push_back( feat ); } } } CHECKPOINT_M("Getting the final evaluation stage stats."); StageStats stats; STATSMAN->GetFinalEvalStageStats( stats ); // Find Machine Category Records FOREACH_ENUM( RankingCategory, rc ) { if( !CATEGORY_RECORD_FEATS ) continue; HighScoreList &hsl = PROFILEMAN->GetMachineProfile()->GetCategoryHighScoreList( st, rc ); for( unsigned j=0; jGetCategoryHighScoreList( st, rc ); for( unsigned j=0; jm_CourseDifficulty; // Find Machine Records { Profile* pProfile = PROFILEMAN->GetMachineProfile(); HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, pTrail ); for( unsigned i=0; iGetDisplayFullTitle().c_str() ); if( cd != Difficulty_Medium ) feat.Feat += " " + CourseDifficultyToLocalizedString(cd); feat.pStringToFill = hs.GetNameMutable(); feat.grade = Grade_NoData; feat.iScore = hs.GetScore(); feat.fPercentDP = hs.GetPercentDP(); if( pCourse->HasBanner() ) feat.Banner = pCourse->GetBannerPath(); asFeatsOut.push_back( feat ); } } // Find Personal Records if( PROFILE_RECORD_FEATS && PROFILEMAN->IsPersistentProfile(pn) ) { HighScoreList &hsl = pProf->GetCourseHighScoreList( pCourse, pTrail ); for( unsigned i=0; iGetDisplayFullTitle().c_str() ); feat.pStringToFill = hs.GetNameMutable(); feat.grade = Grade_NoData; feat.iScore = hs.GetScore(); feat.fPercentDP = hs.GetPercentDP(); if( pCourse->HasBanner() ) feat.Banner = pCourse->GetBannerPath(); asFeatsOut.push_back( feat ); } } } break; default: FAIL_M(ssprintf("Invalid play mode: %i", int(m_PlayMode))); } } bool GameState::AnyPlayerHasRankingFeats() const { std::vector vFeats; FOREACH_PlayerNumber( p ) { GetRankingFeats( p, vFeats ); if( !vFeats.empty() ) return true; } return false; } void GameState::StoreRankingName( PlayerNumber pn, RString sName ) { // The theme can upper it if desired. -Kyz // sName.MakeUpper(); if( USE_NAME_BLACKLIST ) { RageFile file; if( file.Open(NAME_BLACKLIST_FILE) ) { RString sLine; while( !file.AtEOF() ) { if( file.GetLine(sLine) == -1 ) { LuaHelpers::ReportScriptErrorFmt( "Error reading \"%s\": %s", NAME_BLACKLIST_FILE, file.GetError().c_str() ); break; } sLine.MakeUpper(); if( !sLine.empty() && sName.find(sLine) != std::string::npos ) // name contains a bad word { LOG->Trace( "entered '%s' matches blacklisted item '%s'", sName.c_str(), sLine.c_str() ); sName = ""; break; } } } } std::vector aFeats; GetRankingFeats( pn, aFeats ); for( unsigned i=0; iGetNumPlayersEnabled() <= static_cast(m_sPlayersThatWereFilled.size())) { StepsType st = GetCurrentStyle(pn)->m_StepsType; PlayMode mode = m_PlayMode.Get(); Profile *pProfile = PROFILEMAN->GetMachineProfile(); switch (mode) { case PLAY_MODE_REGULAR: case PLAY_MODE_BATTLE: case PLAY_MODE_RAVE: { // Find unique Song and Steps combinations that were played. // Code is taken from implementation in GetRankingFeats() std::vector vSongAndSteps; for( unsigned i=0; im_vPlayedStageStats.size(); i++ ) { CHECKPOINT_M( ssprintf("%u/%i", i, (int)STATSMAN->m_vPlayedStageStats.size() ) ); SongAndSteps sas; ASSERT( !STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs.empty() ); sas.pSong = STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs[0]; ASSERT( sas.pSong != nullptr ); if ( STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps.empty() ) continue; sas.pSteps = STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps[0]; ASSERT( sas.pSteps != nullptr ); vSongAndSteps.push_back( sas ); } std::vector::iterator toDelete = std::unique( vSongAndSteps.begin(), vSongAndSteps.end() ); vSongAndSteps.erase(toDelete, vSongAndSteps.end()); for (unsigned i = 0; i < vSongAndSteps.size(); ++i) { HighScoreList &hsl = pProfile->GetStepsHighScoreList(vSongAndSteps[i].pSong,vSongAndSteps[i].pSteps); if (!PREFSMAN->m_bAllowMultipleHighScoreWithSameName) { // erase all but the highest score for each name hsl.RemoveAllButOneOfEachName(); } hsl.ClampSize(true); } } break; case PLAY_MODE_NONSTOP: case PLAY_MODE_ONI: case PLAY_MODE_ENDLESS: { // Code is taken from implementation in GetRankingFeats() Course* pCourse = m_pCurCourse; ASSERT( pCourse != nullptr ); Trail *pTrail = m_pCurTrail[pn]; ASSERT( pTrail != nullptr ); CourseDifficulty cd = pTrail->m_CourseDifficulty; HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, pTrail ); if (!PREFSMAN->m_bAllowMultipleHighScoreWithSameName) { // erase all but the highest score for each name hsl.RemoveAllButOneOfEachName(); } hsl.ClampSize(true); } break; default: FAIL_M(ssprintf("Invalid play mode: %i", int(m_PlayMode))); } } } bool GameState::AllAreInDangerOrWorse() const { FOREACH_EnabledPlayer( p ) if( m_pPlayerState[p]->m_HealthState < HealthState_Danger ) return false; return true; } bool GameState::OneIsHot() const { FOREACH_EnabledPlayer( p ) if( m_pPlayerState[p]->m_HealthState == HealthState_Hot ) return true; return false; } bool GameState::IsTimeToPlayAttractSounds() const { // m_iNumTimesThroughAttract will be -1 from the first attract screen after // the end of a game until the next time FIRST_ATTRACT_SCREEN is reached. // Play attract sounds for this sort span of time regardless of // m_AttractSoundFrequency because it's awkward to have the machine go // silent immediately after the end of a game. if( m_iNumTimesThroughAttract == -1 ) return true; if( PREFSMAN->m_AttractSoundFrequency == ASF_NEVER ) return false; // play attract sounds once every m_iAttractSoundFrequency times through if( (m_iNumTimesThroughAttract % PREFSMAN->m_AttractSoundFrequency)==0 ) return true; return false; } void GameState::VisitAttractScreen( const RString sScreenName ) { if( sScreenName == CommonMetrics::FIRST_ATTRACT_SCREEN.GetValue() ) m_iNumTimesThroughAttract++; } bool GameState::DifficultiesLocked() const { if( m_PlayMode == PLAY_MODE_RAVE ) return true; if( IsCourseMode() ) return PREFSMAN->m_bLockCourseDifficulties; if( GetCurrentStyle(PLAYER_INVALID)->m_bLockDifficulties ) return true; return false; } bool GameState::ChangePreferredDifficultyAndStepsType( PlayerNumber pn, Difficulty dc, StepsType st ) { m_PreferredDifficulty[pn].Set( dc ); m_PreferredStepsType.Set( st ); if( DifficultiesLocked() ) FOREACH_PlayerNumber( p ) if( p != pn ) m_PreferredDifficulty[p].Set( m_PreferredDifficulty[pn] ); return true; } /* When only displaying difficulties in DIFFICULTIES_TO_SHOW, use GetClosestShownDifficulty * to find which difficulty to show, and ChangePreferredDifficulty(pn, dir) to change * difficulty. */ bool GameState::ChangePreferredDifficulty( PlayerNumber pn, int dir ) { const std::vector &v = CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue(); Difficulty d = GetClosestShownDifficulty(pn); for(;;) { d = enum_add2( d, dir ); if( d < 0 || d >= NUM_Difficulty ) { return false; } if( find(v.begin(), v.end(), d) != v.end() ) { break; // found } } m_PreferredDifficulty[pn].Set( d ); return true; } /* The user may be set to prefer a difficulty that isn't always shown; typically, * Difficulty_Edit. Return the closest shown difficulty <= m_PreferredDifficulty. */ Difficulty GameState::GetClosestShownDifficulty( PlayerNumber pn ) const { const std::vector &v = CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue(); Difficulty iClosest = (Difficulty) 0; int iClosestDist = -1; for (Difficulty const &dc : v) { int iDist = m_PreferredDifficulty[pn] - dc; if( iDist < 0 ) continue; if( iClosestDist != -1 && iDist > iClosestDist ) continue; iClosestDist = iDist; iClosest = dc; } return iClosest; } bool GameState::ChangePreferredCourseDifficultyAndStepsType( PlayerNumber pn, CourseDifficulty cd, StepsType st ) { m_PreferredCourseDifficulty[pn].Set( cd ); m_PreferredStepsType.Set( st ); if( PREFSMAN->m_bLockCourseDifficulties ) FOREACH_PlayerNumber( p ) if( p != pn ) m_PreferredCourseDifficulty[p].Set( m_PreferredCourseDifficulty[pn] ); return true; } bool GameState::ChangePreferredCourseDifficulty( PlayerNumber pn, int dir ) { /* If we have a course selected, only choose among difficulties available in the course. */ const Course *pCourse = m_pCurCourse; const std::vector &v = CommonMetrics::COURSE_DIFFICULTIES_TO_SHOW.GetValue(); CourseDifficulty cd = m_PreferredCourseDifficulty[pn]; for(;;) { cd = enum_add2( cd, dir ); if( cd < 0 || cd >= NUM_Difficulty ) { return false; } if( find(v.begin(),v.end(),cd) == v.end() ) { continue; /* not available */ } if( !pCourse || pCourse->GetTrail( GetCurrentStyle(pn)->m_StepsType, cd ) ) { break; } } return ChangePreferredCourseDifficulty( pn, cd ); } bool GameState::IsCourseDifficultyShown( CourseDifficulty cd ) { const std::vector &v = CommonMetrics::COURSE_DIFFICULTIES_TO_SHOW.GetValue(); return find(v.begin(), v.end(), cd) != v.end(); } Difficulty GameState::GetEasiestStepsDifficulty() const { Difficulty dc = Difficulty_Invalid; FOREACH_HumanPlayer( p ) { if( m_pCurSteps[p] == nullptr ) { LuaHelpers::ReportScriptErrorFmt( "GetEasiestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); continue; } dc = std::min( dc, m_pCurSteps[p]->GetDifficulty() ); } return dc; } Difficulty GameState::GetHardestStepsDifficulty() const { Difficulty dc = Difficulty_Beginner; FOREACH_HumanPlayer( p ) { if( m_pCurSteps[p] == nullptr ) { LuaHelpers::ReportScriptErrorFmt( "GetHardestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); continue; } dc = std::max( dc, m_pCurSteps[p]->GetDifficulty() ); } return dc; } void GameState::SetNewStageSeed() { m_iStageSeed= rand(); } bool GameState::IsEventMode() const { return m_bTemporaryEventMode || PREFSMAN->m_bEventMode; } CoinMode GameState::GetCoinMode() const { // XXX: Event mode shouldn't be valid if CoinMode_Pay if( IsEventMode() && GamePreferences::m_CoinMode == CoinMode_Pay ) return CoinMode_Free; else return GamePreferences::m_CoinMode; } ThemeMetric DISABLE_PREMIUM_IN_EVENT_MODE("GameState","DisablePremiumInEventMode"); Premium GameState::GetPremium() const { return DISABLE_PREMIUM_IN_EVENT_MODE ? Premium_Off : g_Premium; } float GameState::GetGoalPercentComplete( PlayerNumber pn ) { const Profile *pProfile = PROFILEMAN->GetProfile(pn); const StageStats &ssCurrent = STATSMAN->m_CurStageStats; const PlayerStageStats &pssCurrent = ssCurrent.m_player[pn]; float fActual = 0; float fGoal = 0; switch( pProfile->m_GoalType ) { case GoalType_Calories: fActual = pssCurrent.m_fCaloriesBurned; fGoal = (float)pProfile->m_iGoalCalories; break; case GoalType_Time: fActual = ssCurrent.m_fGameplaySeconds; fGoal = (float)pProfile->m_iGoalSeconds; break; case GoalType_None: return 0; // never complete default: FAIL_M(ssprintf("Invalid GoalType: %i", pProfile->m_GoalType)); } if( fGoal == 0 ) return 0; else return fActual / fGoal; } bool GameState::PlayerIsUsingModifier( PlayerNumber pn, const RString &sModifier ) { PlayerOptions po = m_pPlayerState[pn]->m_PlayerOptions.GetCurrent(); SongOptions so = m_SongOptions.GetCurrent(); po.FromString( sModifier ); so.FromString( sModifier ); return po == m_pPlayerState[pn]->m_PlayerOptions.GetCurrent() && so == m_SongOptions.GetCurrent(); } Profile* GameState::GetEditLocalProfile() { if( m_sEditLocalProfileID.Get().empty() ) return nullptr; return PROFILEMAN->GetLocalProfile( m_sEditLocalProfileID ); } PlayerNumber GetNextHumanPlayer( PlayerNumber pn ) { for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) if( GAMESTATE->IsHumanPlayer(pn) ) return pn; return PLAYER_INVALID; } PlayerNumber GetNextEnabledPlayer( PlayerNumber pn ) { for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) if( GAMESTATE->IsPlayerEnabled(pn) ) return pn; return PLAYER_INVALID; } PlayerNumber GetNextCpuPlayer( PlayerNumber pn ) { for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) if( GAMESTATE->IsCpuPlayer(pn) ) return pn; return PLAYER_INVALID; } PlayerNumber GetNextPotentialCpuPlayer( PlayerNumber pn ) { for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) if( !GAMESTATE->IsHumanPlayer(pn) ) return pn; return PLAYER_INVALID; } MultiPlayer GetNextEnabledMultiPlayer( MultiPlayer mp ) { for( enum_add(mp, 1); mp < NUM_MultiPlayer; enum_add(mp, 1) ) if( GAMESTATE->IsMultiPlayerEnabled(mp) ) return mp; return MultiPlayer_Invalid; } // lua start #include "LuaBinding.h" #include "Game.h" /** @brief Allow Lua to have access to the GameState. */ class LunaGameState: public Luna { public: DEFINE_METHOD( IsPlayerEnabled, IsPlayerEnabled(Enum::Check(L, 1)) ) DEFINE_METHOD( IsHumanPlayer, IsHumanPlayer(Enum::Check(L, 1)) ) DEFINE_METHOD( GetPlayerDisplayName, GetPlayerDisplayName(Enum::Check(L, 1)) ) DEFINE_METHOD( GetMasterPlayerNumber, GetMasterPlayerNumber() ) DEFINE_METHOD( GetMultiplayer, m_bMultiplayer ) static int SetMultiplayer( T* p, lua_State *L ) { p->m_bMultiplayer = BArg(1); COMMON_RETURN_SELF; } DEFINE_METHOD( InStepEditor, m_bInStepEditor ); DEFINE_METHOD( GetNumMultiplayerNoteFields, m_iNumMultiplayerNoteFields ) DEFINE_METHOD( ShowW1, ShowW1() ) static int SetNumMultiplayerNoteFields( T* p, lua_State *L ) { p->m_iNumMultiplayerNoteFields = IArg(1); COMMON_RETURN_SELF; } static int GetPlayerState( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); p->m_pPlayerState[pn]->PushSelf(L); return 1; } static int GetMultiPlayerState( T* p, lua_State *L ) { MultiPlayer mp = Enum::Check(L, 1); p->m_pMultiPlayerState[mp]->PushSelf(L); return 1; } static int ApplyGameCommand( T* p, lua_State *L ) { PlayerNumber pn = PLAYER_INVALID; if( lua_gettop(L) >= 2 && !lua_isnil(L,2) ) { // Legacy behavior: if an old-style numerical argument // is given, decrement it before trying to parse if( lua_isnumber(L,2) ) { int arg = (int) lua_tonumber( L, 2 ); arg--; LuaHelpers::Push( L, arg ); lua_replace( L, -2 ); } pn = Enum::Check(L, 2); } p->ApplyGameCommand(SArg(1),pn); COMMON_RETURN_SELF; } static int GetCurrentSong( T* p, lua_State *L ) { if(p->m_pCurSong) p->m_pCurSong->PushSelf(L); else lua_pushnil(L); return 1; } static int SetCurrentSong( T* p, lua_State *L ) { if( lua_isnil(L,1) ) { p->m_pCurSong.Set(nullptr); } else { Song *pS = Luna::check( L, 1, true ); p->m_pCurSong.Set( pS ); } COMMON_RETURN_SELF; } static int CanSafelyEnterGameplay(T* p, lua_State* L) { RString reason; bool can= p->CanSafelyEnterGameplay(reason); lua_pushboolean(L, can); LuaHelpers::Push(L, reason); return 2; } static void SetCompatibleStyleOrError(T* p, lua_State* L, StepsType stype, PlayerNumber pn) { if(!p->SetCompatibleStyle(stype, pn)) { luaL_error(L, "No compatible style for steps/trail."); } if(!p->GetCurrentStyle(pn)) { luaL_error(L, "No style set and AutoSetStyle is false, cannot set steps/trail."); } } static int GetCurrentSteps( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); Steps *pSteps = p->m_pCurSteps[pn]; if( pSteps ) { pSteps->PushSelf(L); } else { lua_pushnil(L); } return 1; } static int SetCurrentSteps( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); if(lua_isnil(L,2)) { p->m_pCurSteps[pn].Set(nullptr); } else { Steps *pS = Luna::check(L,2); SetCompatibleStyleOrError(p, L, pS->m_StepsType, pn); p->m_pCurSteps[pn].Set(pS); p->ForceOtherPlayersToCompatibleSteps(pn); } COMMON_RETURN_SELF; } static int GetCurrentCourse( T* p, lua_State *L ) { if(p->m_pCurCourse) p->m_pCurCourse->PushSelf(L); else lua_pushnil(L); return 1; } static int SetCurrentCourse( T* p, lua_State *L ) { if( lua_isnil(L,1) ) { p->m_pCurCourse.Set(nullptr); } else { Course *pC = Luna::check(L,1); p->m_pCurCourse.Set( pC ); } COMMON_RETURN_SELF; } static int GetCurrentTrail( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); Trail *pTrail = p->m_pCurTrail[pn]; if( pTrail ) { pTrail->PushSelf(L); } else { lua_pushnil(L); } return 1; } static int SetCurrentTrail( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); if(lua_isnil(L,2)) { p->m_pCurTrail[pn].Set(nullptr); } else { Trail *pS = Luna::check(L,2); SetCompatibleStyleOrError(p, L, pS->m_StepsType, pn); p->m_pCurTrail[pn].Set(pS); p->ForceOtherPlayersToCompatibleSteps(pn); } COMMON_RETURN_SELF; } static int GetPreferredSong( T* p, lua_State *L ) { if(p->m_pPreferredSong) p->m_pPreferredSong->PushSelf(L); else lua_pushnil(L); return 1; } static int SetPreferredSong( T* p, lua_State *L ) { if( lua_isnil(L,1) ) { p->m_pPreferredSong = nullptr; } else { Song *pS = Luna::check(L,1); p->m_pPreferredSong = pS; } COMMON_RETURN_SELF; } static int SetTemporaryEventMode( T* p, lua_State *L ) { p->m_bTemporaryEventMode = BArg(1); COMMON_RETURN_SELF; } static int Env( T* p, lua_State *L ) { p->m_Environment->PushSelf(L); return 1; } static int GetEditSourceSteps( T* p, lua_State *L ) { Steps *pSteps = p->m_pEditSourceSteps; if( pSteps ) { pSteps->PushSelf(L); } else { lua_pushnil(L); } return 1; } static int SetPreferredDifficulty( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check( L, 1 ); Difficulty dc = Enum::Check( L, 2 ); p->m_PreferredDifficulty[pn].Set( dc ); COMMON_RETURN_SELF; } DEFINE_METHOD( GetPreferredDifficulty, m_PreferredDifficulty[Enum::Check(L, 1)] ) DEFINE_METHOD( AnyPlayerHasRankingFeats, AnyPlayerHasRankingFeats() ) DEFINE_METHOD( IsCourseMode, IsCourseMode() ) DEFINE_METHOD( IsBattleMode, IsBattleMode() ) DEFINE_METHOD( IsDemonstration, m_bDemonstrationOrJukebox ) DEFINE_METHOD( GetPlayMode, m_PlayMode ) DEFINE_METHOD( GetSortOrder, m_SortOrder ) DEFINE_METHOD( GetCurrentStageIndex, m_iCurrentStageIndex ) DEFINE_METHOD( IsGoalComplete, IsGoalComplete(Enum::Check(L, 1)) ) DEFINE_METHOD( PlayerIsUsingModifier, PlayerIsUsingModifier(Enum::Check(L, 1), SArg(2)) ) DEFINE_METHOD( GetCourseSongIndex, GetCourseSongIndex() ) DEFINE_METHOD( GetLoadingCourseSongIndex, GetLoadingCourseSongIndex() ) DEFINE_METHOD( GetSmallestNumStagesLeftForAnyHumanPlayer, GetSmallestNumStagesLeftForAnyHumanPlayer() ) DEFINE_METHOD( IsAnExtraStage, IsAnExtraStage() ) DEFINE_METHOD( IsExtraStage, IsExtraStage() ) DEFINE_METHOD( IsExtraStage2, IsExtraStage2() ) DEFINE_METHOD( GetCurrentStage, GetCurrentStage() ) DEFINE_METHOD( HasEarnedExtraStage, HasEarnedExtraStage() ) DEFINE_METHOD( GetEarnedExtraStage, GetEarnedExtraStage() ) DEFINE_METHOD( GetEasiestStepsDifficulty, GetEasiestStepsDifficulty() ) DEFINE_METHOD( GetHardestStepsDifficulty, GetHardestStepsDifficulty() ) DEFINE_METHOD( IsEventMode, IsEventMode() ) DEFINE_METHOD( GetNumPlayersEnabled, GetNumPlayersEnabled() ) /*DEFINE_METHOD( GetSongBeat, m_Position.m_fSongBeat ) DEFINE_METHOD( GetSongBeatVisible, m_Position.m_fSongBeatVisible ) DEFINE_METHOD( GetSongBPS, m_Position.m_fCurBPS ) DEFINE_METHOD( GetSongFreeze, m_Position.m_bFreeze ) DEFINE_METHOD( GetSongDelay, m_Position.m_bDelay )*/ static int GetSongPosition( T* p, lua_State *L ) { p->m_Position.PushSelf(L); return 1; } DEFINE_METHOD( GetLastGameplayDuration, m_DanceDuration ) DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn ) DEFINE_METHOD( GetCoins, m_iCoins ) DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check(L, 1)] ) DEFINE_METHOD( GetCoinsNeededToJoin, GetCoinsNeededToJoin() ) DEFINE_METHOD( EnoughCreditsToJoin, EnoughCreditsToJoin() ) DEFINE_METHOD( PlayersCanJoin, PlayersCanJoin() ) DEFINE_METHOD( GetNumSidesJoined, GetNumSidesJoined() ) DEFINE_METHOD( GetCoinMode, GetCoinMode() ) DEFINE_METHOD( GetPremium, GetPremium() ) DEFINE_METHOD( GetSongOptionsString, m_SongOptions.GetCurrent().GetString() ) static int GetSongOptions( T* p, lua_State *L ) { ModsLevel m = Enum::Check( L, 1 ); RString s = p->m_SongOptions.Get(m).GetString(); LuaHelpers::Push( L, s ); return 1; } static int GetSongOptionsObject( T* p, lua_State *L ) { ModsLevel m = Enum::Check( L, 1 ); p->m_SongOptions.Get(m).PushSelf(L); return 1; } static int GetDefaultSongOptions( T* p, lua_State *L ) { SongOptions so; p->GetDefaultSongOptions( so ); lua_pushstring(L, so.GetString()); return 1; } static int ApplyPreferredSongOptionsToOtherLevels(T* p, lua_State* L) { p->m_SongOptions.Assign(ModsLevel_Preferred, p->m_SongOptions.Get(ModsLevel_Preferred)); return 0; } static int ApplyStageModifiers( T* p, lua_State *L ) { p->ApplyStageModifiers( Enum::Check(L, 1), SArg(2) ); COMMON_RETURN_SELF; } static int ApplyPreferredModifiers( T* p, lua_State *L ) { p->ApplyPreferredModifiers( Enum::Check(L, 1), SArg(2) ); COMMON_RETURN_SELF; } static int ClearStageModifiersIllegalForCourse( T* p, lua_State *L ) { p->ClearStageModifiersIllegalForCourse(); COMMON_RETURN_SELF; } static int SetSongOptions( T* p, lua_State *L ) { ModsLevel m = Enum::Check( L, 1 ); SongOptions so; so.FromString( SArg(2) ); p->m_SongOptions.Assign( m, so ); COMMON_RETURN_SELF; } static int GetStageResult( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); LuaHelpers::Push( L, p->GetStageResult(pn) ); return 1; } static int IsWinner( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); lua_pushboolean(L, p->GetStageResult(pn)==RESULT_WIN); return 1; } static int IsDraw( T* p, lua_State *L ) { lua_pushboolean(L, p->GetStageResult(PLAYER_1)==RESULT_DRAW); return 1; } static int GetCurrentGame( T* p, lua_State *L ) { const_cast(p->GetCurrentGame())->PushSelf( L ); return 1; } DEFINE_METHOD( GetEditCourseEntryIndex, m_iEditCourseEntryIndex ) DEFINE_METHOD( GetEditLocalProfileID, m_sEditLocalProfileID.Get() ) static int GetEditLocalProfile( T* p, lua_State *L ) { Profile *pProfile = p->GetEditLocalProfile(); if( pProfile ) pProfile->PushSelf(L); else lua_pushnil( L ); return 1; } static int GetCurrentStepsCredits( T* t, lua_State *L ) { const Song* pSong = t->m_pCurSong; if( pSong == nullptr ) return 0; // use a vector and not a set so that ordering is maintained std::vector vpStepsToShow; FOREACH_HumanPlayer( p ) { const Steps* pSteps = GAMESTATE->m_pCurSteps[p]; if( pSteps == nullptr ) return 0; bool bAlreadyAdded = find( vpStepsToShow.begin(), vpStepsToShow.end(), pSteps ) != vpStepsToShow.end(); if( !bAlreadyAdded ) vpStepsToShow.push_back( pSteps ); } for( unsigned i=0; im_StepsType, pSteps->GetDifficulty(), CourseType_Invalid ) ); lua_pushstring( L, sDifficulty ); lua_pushstring( L, pSteps->GetDescription() ); } return vpStepsToShow.size()*2; } static int SetPreferredSongGroup( T* p, lua_State *L ) { p->m_sPreferredSongGroup.Set( SArg(1) ); COMMON_RETURN_SELF; } DEFINE_METHOD( GetPreferredSongGroup, m_sPreferredSongGroup.Get() ); static int GetHumanPlayers( T* p, lua_State *L ) { std::vector vHP; FOREACH_HumanPlayer( pn ) vHP.push_back( pn ); LuaHelpers::CreateTableFromArray( vHP, L ); return 1; } static int GetEnabledPlayers(T* , lua_State *L ) { std::vector vEP; FOREACH_EnabledPlayer( pn ) vEP.push_back( pn ); LuaHelpers::CreateTableFromArray( vEP, L ); return 1; } static int GetCurrentStyle( T* p, lua_State *L ) { PlayerNumber pn= Enum::Check(L, 1, true, true); Style *pStyle = const_cast