diff --git a/src/CMakeData-data.cmake b/src/CMakeData-data.cmake index 6670c0214c..e8e8e760ea 100644 --- a/src/CMakeData-data.cmake +++ b/src/CMakeData-data.cmake @@ -179,6 +179,7 @@ list(APPEND SM_DATA_REST_SRC "DateTime.cpp" "Difficulty.cpp" "DisplaySpec.cpp" + "EditModePlayerManager.cpp" "EnumHelper.cpp" "Game.cpp" "GameCommand.cpp" @@ -230,6 +231,7 @@ list(APPEND SM_DATA_REST_HPP "DateTime.h" "DisplaySpec.h" "Difficulty.h" + "EditModePlayerManager.h" "EnumHelper.h" "Game.h" "GameCommand.h" diff --git a/src/EditModePlayerManager.cpp b/src/EditModePlayerManager.cpp new file mode 100644 index 0000000000..26c2c8ef10 --- /dev/null +++ b/src/EditModePlayerManager.cpp @@ -0,0 +1,92 @@ +#include "global.h" +#include "GameplayAssist.h" +#include "GamePreferences.h" +#include "GameState.h" +#include "Player.h" +#include "PlayerState.h" +#include "ScreenDimensions.h" +#include "Style.h" +#include "EditModePlayerManager.h" + + +void EditModePlayerManager::AddPlayers(const NoteData& note_data) { + FOREACH_EnabledPlayer(pn){ + players_[pn] = std::make_shared(); + + PlayerPlus& player = *players_[pn]; + player->Init("Player", GAMESTATE->m_pPlayerState[pn], nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + player.Load(note_data); + + player->CacheAllUsedNoteSkins(); + GAMESTATE->m_pPlayerState[pn]->m_PlayerController = PC_HUMAN; + player->SetY(SCREEN_CENTER_Y); + player->SetZoom(SCREEN_HEIGHT / 480); + StyleType style_type = GAMESTATE->GetCurrentStyle(pn)->m_StyleType; + player->SetX(THEME->GetMetricF("ScreenGameplay", ssprintf("PlayerP%d%sX", pn + 1, StyleTypeToString(style_type).c_str()))); + } + + SetVisible(false); +} + + +void EditModePlayerManager::AddPlayersToActorFrame(ActorFrame& frame) { + for (auto& player : players_) { + frame.AddChild(*player.second); + } +} + + +void EditModePlayerManager::ReloadNoteData(const NoteData& note_data) { + for (auto& player : players_) { + player.second->Load(note_data); + } +} + + +void EditModePlayerManager::SetVisible(bool visible) { + for (auto& player : players_) { + (*player.second)->SetVisible(visible); + } +} + +bool EditModePlayerManager::HandleGameplayInput(const InputEventPlus& input, const GameButtonType& gbt) { + if (gbt == GameButtonType_Step && GAMESTATE->IsPlayerEnabled(input.pn)) { + if (GAMESTATE->m_pPlayerState[input.pn]->m_PlayerController == PC_AUTOPLAY) { + return false; + } + const int iCol = GAMESTATE->GetCurrentStyle(input.pn)->GameInputToColumn(input.GameI); + if (iCol != -1) { + (*players_[input.pn])->Step(iCol, -1, input.DeviceI.ts, false, input.type == IET_RELEASE); + } + return true; + } + return false; +} + +void EditModePlayerManager::SetupAutoplay() { + for (auto& player : players_) { + if (GAMESTATE->m_pPlayerState[player.first]->m_PlayerOptions.GetCurrent().m_fPlayerAutoPlay != 0) { + GAMESTATE->m_pPlayerState[player.first]->m_PlayerController = PC_AUTOPLAY; + } + else { + GAMESTATE->m_pPlayerState[player.first]->m_PlayerController = GamePreferences::m_AutoPlay; + } + } +} + +void EditModePlayerManager::CacheAllUsedNoteSkins() { + for (auto& player : players_) { + (*player.second)->CacheAllUsedNoteSkins(); + } +} + + +void EditModePlayerManager::PlayTicks(GameplayAssist& gameplay_assist) { + for (auto& player : players_) { + // Edit mode does not support 2p practicing different steps, so + // enabling one set of ticks is fine. + gameplay_assist.PlayTicks((*player.second)->GetNoteData(), (*player.second)->GetPlayerState()); + return; + } + +} diff --git a/src/EditModePlayerManager.h b/src/EditModePlayerManager.h new file mode 100644 index 0000000000..40ce4d5f15 --- /dev/null +++ b/src/EditModePlayerManager.h @@ -0,0 +1,49 @@ + +#include "global.h" + +#include "ActorFrame.h" +#include "GameplayAssist.h" +#include "Player.h" +#include "NoteData.h" + +#include +#include +#include +#include + +// This class is intended to manage player state during edit mode playback, +// aka practice mode. +class EditModePlayerManager +{ +public: + + // Adds players based on the current gamestate. + void AddPlayers(const NoteData& note_data); + + // Adds the players (and their notefields) to the desired actor frame. + void AddPlayersToActorFrame(ActorFrame& frame); + + // Reload the note data for each player. Intended to be called + // just before playback begins. + void ReloadNoteData(const NoteData& note_data); + + // Toggles visiblity of the player(s) notefield. + void SetVisible(bool visible); + + // During playback, if a player hits a button, handle the input. Only looks + // at inputs related to gameplay, not menuing. + bool HandleGameplayInput(const InputEventPlus& input, const GameButtonType& gbt); + + // If autoplay is enabled, force the player's state into autoplay mode. + void SetupAutoplay(); + + // Player::CacheAllUsedNoteSkins on each player. + void CacheAllUsedNoteSkins(); + + // Play assist ticks. + void PlayTicks(GameplayAssist& gameplay_assist); + +private: + // All players that the manager is looking at. Indexable by PlayerNumber. + std::unordered_map> players_; +}; diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index ee94f33bf0..5e768031f3 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -1404,9 +1404,10 @@ void ScreenEdit::Init() GAMESTATE->m_bInStepEditor = true; SubscribeToMessage( "Judgment" ); + main_player_ = GAMESTATE->GetMasterPlayerNumber(); ASSERT( GAMESTATE->m_pCurSong != nullptr ); - ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != nullptr ); + ASSERT( GAMESTATE->m_pCurSteps[main_player_] != nullptr ); EDIT_MODE.Load( m_sName, "EditMode" ); ScreenWithMenuElements::Init(); @@ -1422,14 +1423,10 @@ void ScreenEdit::Init() m_InputPlayerNumber = PLAYER_INVALID; if( GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->m_StyleType == StyleType_TwoPlayersSharedSides ) - m_InputPlayerNumber = PLAYER_1; - - FOREACH_PlayerNumber( p ) - GAMESTATE->m_bSideIsJoined[p] = false; - GAMESTATE->m_bSideIsJoined[PLAYER_1] = true; + m_InputPlayerNumber = main_player_; m_pSong = GAMESTATE->m_pCurSong; - m_pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + m_pSteps = GAMESTATE->m_pCurSteps[main_player_]; /* The user will most likely switch into Step Timing after laying down some initial notes. It also throws off many people at first glance. @@ -1476,20 +1473,22 @@ void ScreenEdit::Init() ModsLevel_Preferred, m_sNoteSkin, sNoteSkin ); } } - m_PlayerStateEdit.SetPlayerNumber(PLAYER_1); + + m_PlayerStateEdit.SetPlayerNumber(main_player_); m_PlayerStateEdit.m_NotefieldZoom= 1.0f; + // If we always go with the GAMESTATE NoteSkin, we will have fun effects // like Vivid or Flat in the editor notefield. This is not conducive to // productive editing. // todo: We should allow certain noteskins (note-colored/rhythm) to be // displayed. (Perhaps this should be a noteskin metric.) -aj - if( NOTESKIN->DoesNoteSkinExist( EDITOR_NOTE_SKINS[PLAYER_1].Get() ) ) + if( NOTESKIN->DoesNoteSkinExist( EDITOR_NOTE_SKINS[main_player_].Get() ) ) { - PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, EDITOR_NOTE_SKINS[PLAYER_1].Get() ); + PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, EDITOR_NOTE_SKINS[main_player_].Get() ); } else { - PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetStage().m_sNoteSkin ); + PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, GAMESTATE->m_pPlayerState[main_player_]->m_PlayerOptions.GetStage().m_sNoteSkin ); } m_PlayerStateEdit.m_PlayerOptions.FromString( ModsLevel_Stage, EDIT_MODIFIERS ); @@ -1504,7 +1503,7 @@ void ScreenEdit::Init() m_NoteDataRecord.SetNumTracks( m_NoteDataEdit.GetNumTracks() ); m_NoteFieldRecord.SetXY( RECORD_X, RECORD_Y ); - m_NoteFieldRecord.Init( GAMESTATE->m_pPlayerState[PLAYER_1], PLAYER_HEIGHT ); + m_NoteFieldRecord.Init( GAMESTATE->m_pPlayerState[main_player_], PLAYER_HEIGHT ); m_NoteFieldRecord.Load( &m_NoteDataRecord, -120, 425 ); this->AddChild( &m_NoteFieldRecord ); @@ -1527,12 +1526,8 @@ void ScreenEdit::Init() SetDirty(true); } - m_Player->Init( "Player", GAMESTATE->m_pPlayerState[PLAYER_1], nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr ); - m_Player->CacheAllUsedNoteSkins(); - GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = PC_HUMAN; - m_Player->SetXY( PLAYER_X, PLAYER_Y ); - m_Player->SetZoom( SCREEN_HEIGHT/480 ); - this->AddChild( m_Player ); + player_manager_.AddPlayers(m_NoteDataEdit); + player_manager_.AddPlayersToActorFrame(*this); this->AddChild( &m_Foreground ); @@ -1609,8 +1604,7 @@ void ScreenEdit::PlayTicks() { if( m_EditState != STATE_PLAYING ) return; - - m_GameplayAssist.PlayTicks( m_Player->GetNoteData(), m_Player->GetPlayerState() ); + player_manager_.PlayTicks(m_GameplayAssist); } static ThemeMetric FADE_IN_PREVIEW("ScreenEdit", "FadeInPreview"); @@ -3229,34 +3223,9 @@ bool ScreenEdit::InputPlay( const InputEventPlus &input, EditButton EditB ) } GameButtonType gbt = GAMESTATE->m_pCurGame->GetPerButtonInfo(input.GameI.button)->m_gbt; - - if( GamePreferences::m_AutoPlay == PC_HUMAN && GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fPlayerAutoPlay == 0 ) - { - const int iCol = GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->GameInputToColumn( input.GameI ); - bool bRelease = input.type == IET_RELEASE; - switch( input.pn ) - { - case PLAYER_2: - // ignore player 2 input unless this mode requires it - if( GAMESTATE->GetCurrentStyle(GAMESTATE->GetMasterPlayerNumber())->m_StyleType != StyleType_TwoPlayersSharedSides ) - break; - - [[fallthrough]]; - case PLAYER_1: - { - switch( gbt ) - { - case GameButtonType_Step: - if( iCol != -1 ) - m_Player->Step( iCol, -1, input.DeviceI.ts, false, bRelease ); - return true; - default: - break; - } - } - default: break; - } - } + if (player_manager_.HandleGameplayInput(input, gbt)) { + return true; + }; if( gbt == GameButtonType_Menu && input.type == IET_FIRST_PRESS ) { @@ -3347,7 +3316,7 @@ void ScreenEdit::TransitionEditState( EditState em ) case STATE_PLAYING: AdjustSync::HandleSongEnd(); if (!GAMESTATE->m_bIsUsingStepTiming) - GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = backupStepTiming; + GAMESTATE->m_pCurSteps[main_player_]->m_Timing = backupStepTiming; if( AdjustSync::IsSyncDataChanged() ) ScreenSaveSync::PromptSaveSync(); break; @@ -3374,12 +3343,14 @@ void ScreenEdit::TransitionEditState( EditState em ) if( em != STATE_EDITING ) { // Stop displaying course attacks, if any. - GAMESTATE->m_pPlayerState[PLAYER_1]->RemoveActiveAttacks(); - // Load the player's default PlayerOptions. - GAMESTATE->m_pPlayerState[PLAYER_1]->RebuildPlayerOptionsFromActiveAttacks(); + FOREACH_EnabledPlayer(pn) { + GAMESTATE->m_pPlayerState[pn]->RemoveActiveAttacks(); + // Load the player's default PlayerOptions. + GAMESTATE->m_pPlayerState[pn]->RebuildPlayerOptionsFromActiveAttacks(); - // Snap to current options. - GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.SetCurrentToLevel( ModsLevel_Stage ); + // Snap to current options. + GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.SetCurrentToLevel(ModsLevel_Stage); + } } switch( em ) @@ -3413,8 +3384,8 @@ void ScreenEdit::TransitionEditState( EditState em ) { // Substitute the song timing for the step timing during // preview if we're in song mode - backupStepTiming = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing; - GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing.Clear(); + backupStepTiming = GAMESTATE->m_pCurSteps[main_player_]->m_Timing; + GAMESTATE->m_pCurSteps[main_player_]->m_Timing.Clear(); } /* Reset the note skin, in case preferences have changed. */ @@ -3434,28 +3405,25 @@ void ScreenEdit::TransitionEditState( EditState em ) case STATE_PLAYING: // If we're in course display mode, set that up. SetupCourseAttacks(); + player_manager_.SetupAutoplay(); + player_manager_.ReloadNoteData(m_NoteDataEdit); - m_Player.Load( m_NoteDataEdit ); - - if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fPlayerAutoPlay != 0 ) - GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = PC_AUTOPLAY; - else - GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = GamePreferences::m_AutoPlay; - - if( g_bEditorShowBGChangesPlay ) + if (g_bEditorShowBGChangesPlay) { /* FirstBeat affects backgrounds, so commit changes to memory (not to disk) * and recalc it. */ - Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; - ASSERT( pSteps != nullptr ); - pSteps->SetNoteData( m_NoteDataEdit ); + Steps* pSteps = GAMESTATE->m_pCurSteps[main_player_]; + ASSERT(pSteps != nullptr); + pSteps->SetNoteData(m_NoteDataEdit); m_pSong->ReCalculateRadarValuesAndLastSecond(); + // TODO: Background videos don't support seeking, when they do, make sure + // to load the appropriate part of the video. m_Background.Unload(); - m_Background.LoadFromSong( m_pSong ); + m_Background.LoadFromSong(m_pSong); m_Foreground.Unload(); - m_Foreground.LoadFromSong( m_pSong ); + m_Foreground.LoadFromSong(m_pSong); } break; @@ -3489,7 +3457,8 @@ void ScreenEdit::TransitionEditState( EditState em ) m_SnapDisplay.SetVisible( em == STATE_EDITING ); m_NoteFieldEdit.SetVisible( em == STATE_EDITING ); m_NoteFieldRecord.SetVisible( em == STATE_RECORDING || em == STATE_RECORDING_PAUSED ); - m_Player->SetVisible( em == STATE_PLAYING ); + + player_manager_.SetVisible(em == STATE_PLAYING); m_Foreground.SetVisible( g_bEditorShowBGChangesPlay && em != STATE_EDITING ); switch( em ) @@ -3954,7 +3923,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) { // The options may have changed the note skin. m_NoteFieldRecord.CacheAllUsedNoteSkins(); - m_Player->CacheAllUsedNoteSkins(); + player_manager_.CacheAllUsedNoteSkins(); // stop any music that screen may have been playing SOUND->StopMusic(); @@ -4391,7 +4360,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) for (Steps *s : apSteps) { // If we're not on the same style, let it go. - if( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType != s->m_StepsType ) + if( GAMESTATE->m_pCurSteps[main_player_]->m_StepsType != s->m_StepsType ) continue; // If autogenned, it isn't being saved. if( s->IsAutogen() ) @@ -4411,8 +4380,8 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) pSong->DeleteSteps( pSteps ); if( m_pSteps == pSteps ) m_pSteps = nullptr; - if( GAMESTATE->m_pCurSteps[PLAYER_1].Get() == pSteps ) - GAMESTATE->m_pCurSteps[PLAYER_1].Set(nullptr); + if( GAMESTATE->m_pCurSteps[main_player_].Get() == pSteps ) + GAMESTATE->m_pCurSteps[main_player_].Set(nullptr); } @@ -6164,13 +6133,14 @@ void ScreenEdit::SetupCourseAttacks() void ScreenEdit::CopyToLastSave() { - ASSERT( GAMESTATE->m_pCurSong != nullptr ); - ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != nullptr ); + ASSERT(GAMESTATE->m_pCurSteps[main_player_] != nullptr); + m_SongLastSave = *GAMESTATE->m_pCurSong; m_vStepsLastSave.clear(); - const std::vector &vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType ); - for (Steps *it : vSteps) - m_vStepsLastSave.push_back( *it ); + const std::vector& vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType(GAMESTATE->m_pCurSteps[main_player_]->m_StepsType); + for (Steps* it : vSteps) { + m_vStepsLastSave.push_back(*it); + } } void ScreenEdit::CopyFromLastSave() @@ -6179,10 +6149,11 @@ void ScreenEdit::CopyFromLastSave() // 1) No steps can be created by ScreenEdit // 2) No steps can be deleted by ScreenEdit (except possibly when we exit) *GAMESTATE->m_pCurSong = m_SongLastSave; - const std::vector &vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType ); - ASSERT_M( vSteps.size() == m_vStepsLastSave.size(), ssprintf("Step sizes don't match: %d, %d", int(vSteps.size()), int(m_vStepsLastSave.size())) ); - for( unsigned i = 0; i < vSteps.size(); ++i ) + const std::vector& vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType(GAMESTATE->m_pCurSteps[main_player_]->m_StepsType); + ASSERT_M(vSteps.size() == m_vStepsLastSave.size(), ssprintf("Step sizes don't match: %d, %d", int(vSteps.size()), int(m_vStepsLastSave.size()))); + for (unsigned i = 0; i < vSteps.size(); ++i) { *vSteps[i] = m_vStepsLastSave[i]; + } } void ScreenEdit::RevertFromDisk() diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index 0d0072f501..063e913caa 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -1,6 +1,7 @@ #ifndef SCREEN_EDIT_H #define SCREEN_EDIT_H +#include "EditModePlayerManager.h" #include "ScreenWithMenuElements.h" #include "BitmapText.h" #include "Player.h" @@ -285,6 +286,7 @@ protected: Steps* m_pSteps; PlayerNumber m_InputPlayerNumber; + PlayerNumber main_player_; PlayerState m_PlayerStateEdit; NoteField m_NoteFieldEdit; NoteData m_NoteDataEdit; @@ -376,8 +378,8 @@ protected: bool m_bRemoveNoteButtonDown; // for MODE_PLAY + EditModePlayerManager player_manager_; void SetupCourseAttacks(); - PlayerPlus m_Player; Background m_Background; Foreground m_Foreground; bool m_bReturnToRecordMenuAfterPlay; diff --git a/src/ScreenMiniMenu.cpp b/src/ScreenMiniMenu.cpp index 5ea1c7906b..6689cfe3c6 100644 --- a/src/ScreenMiniMenu.cpp +++ b/src/ScreenMiniMenu.cpp @@ -127,12 +127,14 @@ void ScreenMiniMenu::ImportOptions( int r, const std::vector &vpns optrow.SetOneSharedSelection( mr.iDefaultChoice ); } -void ScreenMiniMenu::ExportOptions( int r, const std::vector &vpns ) +void ScreenMiniMenu::ExportOptions(int r, const std::vector& vpns) { - if( r == GetCurrentRow() ) + PlayerNumber main_player = vpns.empty() ? PLAYER_1 : vpns.front(); + if (r == GetCurrentRow(main_player)) { s_iLastRowCode = m_vMenuRows[r].iRowCode; - s_viLastAnswers.resize( m_vMenuRows.size() ); - s_viLastAnswers[r] = m_pRows[r]->GetOneSharedSelection( true ); + } + s_viLastAnswers.resize(m_vMenuRows.size()); + s_viLastAnswers[r] = m_pRows[r]->GetOneSharedSelection(true); } void ScreenMiniMenu::HandleScreenMessage( const ScreenMessage SM )