Optionally load in P2 during PracticeMode playback.

Use the first PlayerNumber that comes in under export options, this fixes P2
loading by itself in PracticeMode.
This commit is contained in:
Brandon W
2024-09-22 10:59:06 -07:00
committed by teejusb
parent 0aa41b305d
commit 5bb277b5de
6 changed files with 205 additions and 87 deletions
+2
View File
@@ -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"
+92
View File
@@ -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>();
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;
}
}
+49
View File
@@ -0,0 +1,49 @@
#include "global.h"
#include "ActorFrame.h"
#include "GameplayAssist.h"
#include "Player.h"
#include "NoteData.h"
#include <iterator>
#include <map>
#include <unordered_map>
#include <vector>
// 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<PlayerNumber, std::shared_ptr<PlayerPlus>> players_;
};
+53 -82
View File
@@ -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<float> 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<Steps*> &vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType );
for (Steps *it : vSteps)
m_vStepsLastSave.push_back( *it );
const std::vector<Steps*>& 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<Steps*> &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<Steps*>& 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()
+3 -1
View File
@@ -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;
+6 -4
View File
@@ -127,12 +127,14 @@ void ScreenMiniMenu::ImportOptions( int r, const std::vector<PlayerNumber> &vpns
optrow.SetOneSharedSelection( mr.iDefaultChoice );
}
void ScreenMiniMenu::ExportOptions( int r, const std::vector<PlayerNumber> &vpns )
void ScreenMiniMenu::ExportOptions(int r, const std::vector<PlayerNumber>& 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 )