From f0f71b0055d38ca7f0011d0e57d98a3f460962d4 Mon Sep 17 00:00:00 2001 From: Barinade Date: Sat, 10 Aug 2019 00:50:51 -0500 Subject: [PATCH] Split ScreenGameplay code into multiple files PlayerInfo is its own file Practice/Replay specific functions are part of their own class --- src/Etterna/Models/CMakeLists.txt | 2 + src/Etterna/Models/Misc/PlayerInfo.cpp | 138 +++++ src/Etterna/Models/Misc/PlayerInfo.h | 100 +++ .../Screen/Gameplay/ScreenGameplay.cpp | 585 +----------------- src/Etterna/Screen/Gameplay/ScreenGameplay.h | 106 +--- .../Gameplay/ScreenGameplayPractice.cpp | 211 +++++++ .../Screen/Gameplay/ScreenGameplayPractice.h | 26 + .../Screen/Gameplay/ScreenGameplayReplay.cpp | 470 ++++++++++++++ .../Screen/Gameplay/ScreenGameplayReplay.h | 24 + 9 files changed, 973 insertions(+), 689 deletions(-) create mode 100644 src/Etterna/Models/Misc/PlayerInfo.cpp create mode 100644 src/Etterna/Models/Misc/PlayerInfo.h diff --git a/src/Etterna/Models/CMakeLists.txt b/src/Etterna/Models/CMakeLists.txt index 17457f189d..bd0483917b 100644 --- a/src/Etterna/Models/CMakeLists.txt +++ b/src/Etterna/Models/CMakeLists.txt @@ -124,6 +124,7 @@ list(APPEND REST_SRC "Misc/NoteTypes.cpp" "Misc/OptionRowHandler.cpp" "Misc/PlayerAI.cpp" + "Misc/PlayerInfo.cpp" "Misc/PlayerNumber.cpp" "Misc/PlayerOptions.cpp" "Misc/PlayerStageStats.cpp" @@ -178,6 +179,7 @@ list(APPEND REST_HPP "Misc/NoteTypes.h" "Misc/OptionRowHandler.h" "Misc/PlayerAI.h" + "Misc/PlayerInfo.h" "Misc/PlayerNumber.h" "Misc/PlayerOptions.h" "Misc/PlayerStageStats.h" diff --git a/src/Etterna/Models/Misc/PlayerInfo.cpp b/src/Etterna/Models/Misc/PlayerInfo.cpp new file mode 100644 index 0000000000..03461a09c2 --- /dev/null +++ b/src/Etterna/Models/Misc/PlayerInfo.cpp @@ -0,0 +1,138 @@ +#include "Etterna/Globals/global.h" +#include "PlayerInfo.h" +#include "GameConstantsAndTypes.h" +#include "PlayerNumber.h" +#include "PlayerState.h" +#include "Etterna/Singletons/Gamestate.h" +#include "Etterna/Singletons/StatsManager.h" +#include "PlayerStageStats.h" +#include "Etterna/Models/ScoreKeepers/ScoreKeeper.h" +#include "Etterna/Actor/Gameplay/Player.h" +#include "Etterna/Actor/Gameplay/LifeMeter.h" + +#include "Etterna/Models/Lua/LuaBinding.h" +#include "Etterna/Singletons/LuaManager.h" + +static ThemeMetric SCORE_KEEPER_CLASS("ScreenGameplay", + "ScoreKeeperClass"); + +PlayerInfo::PlayerInfo() + : m_pn(PLAYER_INVALID) + , m_SoundEffectControl() + , m_vpStepsQueue() + , m_pLifeMeter(NULL) + , m_ptextStepsDescription(NULL) + , m_pPrimaryScoreKeeper(NULL) + , m_ptextPlayerOptions(NULL) + , m_NoteData() + , m_pPlayer(NULL) + , m_pStepsDisplay(NULL) +{ +} + +void +PlayerInfo::Load(PlayerNumber pn, + MultiPlayer mp, + bool bShowNoteField, + int iAddToDifficulty) +{ + m_pn = pn; + m_mp = mp; + m_bPlayerEnabled = IsEnabled(); + m_bIsDummy = false; + m_iAddToDifficulty = iAddToDifficulty; + m_pLifeMeter = NULL; + m_ptextStepsDescription = NULL; + + if (!IsMultiPlayer()) { + PlayMode mode = GAMESTATE->m_PlayMode; + switch (mode) { + case PLAY_MODE_REGULAR: + break; + default: + FAIL_M(ssprintf("Invalid PlayMode: %i", mode)); + } + } + + PlayerState* const pPlayerState = GetPlayerState(); + PlayerStageStats* const pPlayerStageStats = GetPlayerStageStats(); + m_pPrimaryScoreKeeper = ScoreKeeper::MakeScoreKeeper( + SCORE_KEEPER_CLASS, pPlayerState, pPlayerStageStats); + + m_ptextPlayerOptions = NULL; + m_pPlayer = new Player(m_NoteData, bShowNoteField); + m_pStepsDisplay = NULL; + + if (IsMultiPlayer()) { + pPlayerState->m_PlayerOptions = + GAMESTATE->m_pPlayerState->m_PlayerOptions; + } +} + +PlayerInfo::~PlayerInfo() +{ + SAFE_DELETE(m_pLifeMeter); + SAFE_DELETE(m_ptextStepsDescription); + SAFE_DELETE(m_pPrimaryScoreKeeper); + SAFE_DELETE(m_ptextPlayerOptions); + SAFE_DELETE(m_pPlayer); + SAFE_DELETE(m_pStepsDisplay); +} + +PlayerState* +PlayerInfo::GetPlayerState() +{ + return IsMultiPlayer() + ? GAMESTATE + ->m_pMultiPlayerState[GetPlayerStateAndStageStatsIndex()] + : GAMESTATE->m_pPlayerState; +} + +PlayerStageStats* +PlayerInfo::GetPlayerStageStats() +{ + return &STATSMAN->m_CurStageStats.m_player; +} + +bool +PlayerInfo::IsEnabled() +{ + if (m_pn != PLAYER_INVALID) + return GAMESTATE->IsPlayerEnabled(m_pn); + if (m_mp != MultiPlayer_Invalid) + return GAMESTATE->IsMultiPlayerEnabled(m_mp); + if (m_bIsDummy) + return true; + FAIL_M("Invalid non-dummy player."); +} + +/** @brief Allow Lua to have access to the PlayerInfo. */ +class LunaPlayerInfo : public Luna +{ + public: + static int GetLifeMeter(T* p, lua_State* L) + { + if (p->m_pLifeMeter) { + p->m_pLifeMeter->PushSelf(L); + return 1; + } + return 0; + } + + static int GetStepsQueueWrapped(T* p, lua_State* L) + { + int iIndex = IArg(1); + iIndex %= p->m_vpStepsQueue.size(); + Steps* pSteps = p->m_vpStepsQueue[iIndex]; + pSteps->PushSelf(L); + return 1; + } + + LunaPlayerInfo() + { + ADD_METHOD(GetLifeMeter); + ADD_METHOD(GetStepsQueueWrapped); + } +}; + +LUA_REGISTER_CLASS(PlayerInfo) diff --git a/src/Etterna/Models/Misc/PlayerInfo.h b/src/Etterna/Models/Misc/PlayerInfo.h new file mode 100644 index 0000000000..c49b560987 --- /dev/null +++ b/src/Etterna/Models/Misc/PlayerInfo.h @@ -0,0 +1,100 @@ +#ifndef PLAYERINFO_H +#define PLAYERINFO_H + +#include "Etterna/Models/Misc/GameConstantsAndTypes.h" +#include "Etterna/Models/Misc/PlayerNumber.h" +#include "Etterna/Models/StepsAndStyles/Steps.h" +#include "Etterna/Models/Misc/PlayerStageStats.h" +#include "Etterna/Actor/Base/BitmapText.h" +#include "Etterna/Models/Misc/SoundEffectControl.h" +#include "Etterna/Models/NoteData/NoteData.h" + +class Player; + +class LifeMeter; +class StepsDisplay; +class ScoreKeeper; + +class PlayerInfo +{ + public: + PlayerInfo(); + ~PlayerInfo(); + + void Load(PlayerNumber pn, + MultiPlayer mp, + bool bShowNoteField, + int iAddToDifficulty); + + /** + * @brief Retrieve the player's state and stage stats index. + * @return the player's state and stage stats index. + */ + MultiPlayer GetPlayerStateAndStageStatsIndex() + { + return m_pn == PLAYER_INVALID ? m_mp : static_cast(m_pn); + } + PlayerState* GetPlayerState(); + PlayerStageStats* GetPlayerStageStats(); + PlayerNumber GetStepsAndTrailIndex() + { + return m_pn == PLAYER_INVALID ? PLAYER_1 : m_pn; + } + /** + * @brief Determine if the player information is enabled. + * @return its success or failure. */ + bool IsEnabled(); + /** + * @brief Determine if we're in MultiPlayer. + * @return true if it is MultiPlayer, false otherwise. */ + bool IsMultiPlayer() const { return m_mp != MultiPlayer_Invalid; } + /** + * @brief Retrieve the name of the Player based on the mode. + * @return the name of the Player. */ + RString GetName() const + { + if (m_bIsDummy) + return ssprintf("PlayerInfoDummy"); + if (IsMultiPlayer()) + return MultiPlayerToString(m_mp); + else + return PlayerNumberToString(m_pn); + } + + // Lua + void PushSelf(lua_State* L); + + /** @brief The present Player's number. */ + PlayerNumber m_pn; + /** @brief The present Player's multiplayer number. */ + MultiPlayer m_mp{ MultiPlayer_Invalid }; + bool m_bIsDummy{ false }; + int m_iAddToDifficulty{ 0 }; // if > 0, use the Nth harder Steps + bool m_bPlayerEnabled{ false }; // IsEnabled cache for iterators + SoundEffectControl m_SoundEffectControl; + + /** + * @brief The list of Steps a player has to go through in this set. + * + * The size may be greater than 1 if playing a course. */ + vector m_vpStepsQueue; + + /** @brief The LifeMeter showing a Player's health. */ + LifeMeter* m_pLifeMeter; + /** @brief The description of the current Steps. */ + BitmapText* m_ptextStepsDescription; + /** @brief The primary ScoreKeeper for keeping track of the score. */ + ScoreKeeper* m_pPrimaryScoreKeeper; + /** @brief The current PlayerOptions that are activated. */ + BitmapText* m_ptextPlayerOptions; + /** @brief The current attack modifiers that are in play for the moment. */ + + /** @brief The NoteData the Player has to get through. */ + NoteData m_NoteData; + /** @brief The specific Player that is going to play. */ + Player* m_pPlayer; + + StepsDisplay* m_pStepsDisplay; +}; + +#endif diff --git a/src/Etterna/Screen/Gameplay/ScreenGameplay.cpp b/src/Etterna/Screen/Gameplay/ScreenGameplay.cpp index 64c07ebd7e..4480209e20 100644 --- a/src/Etterna/Screen/Gameplay/ScreenGameplay.cpp +++ b/src/Etterna/Screen/Gameplay/ScreenGameplay.cpp @@ -48,6 +48,7 @@ #include "Etterna/FileTypes/XmlFileUtil.h" #include "Etterna/Singletons/DownloadManager.h" #include "Etterna/Singletons/ScoreManager.h" +#include "Etterna/Models/Misc/PlayerInfo.h" #define SONG_POSITION_METER_WIDTH \ THEME->GetMetricF(m_sName, "SongPositionMeterWidth") @@ -57,8 +58,6 @@ static ThemeMetric INITIAL_BACKGROUND_BRIGHTNESS( "InitialBackgroundBrightness"); static ThemeMetric SECONDS_BETWEEN_COMMENTS("ScreenGameplay", "SecondsBetweenComments"); -static ThemeMetric SCORE_KEEPER_CLASS("ScreenGameplay", - "ScoreKeeperClass"); AutoScreenMessage(SM_PlayGo); @@ -82,98 +81,6 @@ AutoScreenMessage(SM_BattleTrickLevel3); static Preference g_bCenter1Player("Center1Player", true); static Preference g_bShowLyrics("ShowLyrics", false); -PlayerInfo::PlayerInfo() - : m_pn(PLAYER_INVALID) - , m_SoundEffectControl() - , m_vpStepsQueue() - , m_pLifeMeter(NULL) - , m_ptextStepsDescription(NULL) - , m_pPrimaryScoreKeeper(NULL) - , m_ptextPlayerOptions(NULL) - , m_NoteData() - , m_pPlayer(NULL) - , m_pStepsDisplay(NULL) -{ -} - -void -PlayerInfo::Load(PlayerNumber pn, - MultiPlayer mp, - bool bShowNoteField, - int iAddToDifficulty) -{ - m_pn = pn; - m_mp = mp; - m_bPlayerEnabled = IsEnabled(); - m_bIsDummy = false; - m_iAddToDifficulty = iAddToDifficulty; - m_pLifeMeter = NULL; - m_ptextStepsDescription = NULL; - - if (!IsMultiPlayer()) { - PlayMode mode = GAMESTATE->m_PlayMode; - switch (mode) { - case PLAY_MODE_REGULAR: - break; - default: - FAIL_M(ssprintf("Invalid PlayMode: %i", mode)); - } - } - - PlayerState* const pPlayerState = GetPlayerState(); - PlayerStageStats* const pPlayerStageStats = GetPlayerStageStats(); - m_pPrimaryScoreKeeper = ScoreKeeper::MakeScoreKeeper( - SCORE_KEEPER_CLASS, pPlayerState, pPlayerStageStats); - - m_ptextPlayerOptions = NULL; - m_pPlayer = new Player(m_NoteData, bShowNoteField); - m_pStepsDisplay = NULL; - - if (IsMultiPlayer()) { - pPlayerState->m_PlayerOptions = - GAMESTATE->m_pPlayerState->m_PlayerOptions; - } -} - -PlayerInfo::~PlayerInfo() -{ - SAFE_DELETE(m_pLifeMeter); - SAFE_DELETE(m_ptextStepsDescription); - SAFE_DELETE(m_pPrimaryScoreKeeper); - SAFE_DELETE(m_ptextPlayerOptions); - SAFE_DELETE(m_pPlayer); - SAFE_DELETE(m_pStepsDisplay); -} - -PlayerState* -PlayerInfo::GetPlayerState() -{ - return IsMultiPlayer() - ? GAMESTATE - ->m_pMultiPlayerState[GetPlayerStateAndStageStatsIndex()] - : GAMESTATE->m_pPlayerState; -} - -PlayerStageStats* -PlayerInfo::GetPlayerStageStats() -{ - return &STATSMAN->m_CurStageStats.m_player; -} - -bool -PlayerInfo::IsEnabled() -{ - if (m_pn != PLAYER_INVALID) - return GAMESTATE->IsPlayerEnabled(m_pn); - if (m_mp != MultiPlayer_Invalid) - return GAMESTATE->IsMultiPlayerEnabled(m_mp); - if (m_bIsDummy) - return true; - FAIL_M("Invalid non-dummy player."); -} - -//////////////////////////////////////////////////////////////////////////////// - ScreenGameplay::ScreenGameplay() { m_pSongBackground = NULL; @@ -214,7 +121,6 @@ ScreenGameplay::Init() ScreenWithMenuElements::Init(); this->FillPlayerInfo(&m_vPlayerInfo); - int iNumEnabledPlayers = 1; m_pSoundMusic = NULL; @@ -226,7 +132,6 @@ ScreenGameplay::Init() GAMESTATE->BeginStage(); GAMESTATE->SetPaused(false); - m_fReplayBookmarkSeconds = 0.f; unsigned int count = m_vPlayerInfo.m_vpStepsQueue.size(); for (unsigned int i = 0; i < count; i++) { @@ -423,27 +328,6 @@ ScreenGameplay::Init() m_GiveUpTimer.SetZero(); m_gave_up = false; - - // Force FailOff in Practice Mode - if (GAMESTATE->m_pPlayerState->m_PlayerOptions.GetCurrent().m_bPractice) { - GAMEMAN->m_iPreviousFail = - GAMESTATE->m_pPlayerState->m_PlayerOptions.GetPreferred().m_FailType; - GAMESTATE->m_pPlayerState->m_PlayerOptions.GetCurrent().m_FailType = - FailType_Off; - GAMESTATE->m_pPlayerState->m_PlayerOptions.GetSong().m_FailType = - FailType_Off; - GAMESTATE->m_pPlayerState->m_PlayerOptions.GetPreferred().m_FailType = - FailType_Off; - } - - if (!GAMEMAN->m_bRestartedGameplay && - GAMESTATE->m_pPlayerState->m_PlayerOptions.GetCurrent().m_bPractice) { - GAMEMAN->m_bResetModifiers = true; - GAMEMAN->m_fPreviousRate = - GAMESTATE->m_SongOptions.GetPreferred().m_fMusicRate; - GAMEMAN->m_sModsToReset = - GAMESTATE->m_pPlayerState->m_PlayerOptions.GetCurrent().GetString(); - } } bool @@ -927,10 +811,6 @@ ScreenGameplay::Update(float fDeltaTime) Screen::Update(fDeltaTime); } - /* This happens if ScreenDemonstration::HandleScreenMessage sets a new - * screen when PREFSMAN->m_bDelayedScreenLoad. */ - if (GAMESTATE->m_pCurSong == NULL) - return; /* This can happen if ScreenDemonstration::HandleScreenMessage sets a new * screen when !PREFSMAN->m_bDelayedScreenLoad. (The new screen was loaded * when we called Screen::Update, and the ctor might set a new @@ -1608,20 +1488,6 @@ ScreenGameplay::SaveStats() NoteDataWithScoring::GetActualRadarValues(nd, pss, fMusicLen, rv); pss.m_radarActual += rv; GAMESTATE->SetProcessedTimingData(NULL); - - if (GamePreferences::m_AutoPlay.Get() == PC_REPLAY) { - // We need to replace the newly created replay data with the actual old - // data Because to keep consistently lazy practices, we can just hack - // things together instead of fixing the real issue -poco - // (doing this fixes a lot of issues in the eval screen) - PlayerStageStats* pss = m_vPlayerInfo.GetPlayerStageStats(); - HighScore* hs = PlayerAI::pScoreData; - pss->m_vHoldReplayData = hs->GetHoldReplayDataVector(); - pss->m_vNoteRowVector = hs->GetNoteRowVector(); - pss->m_vOffsetVector = hs->GetOffsetVector(); - pss->m_vTapNoteTypeVector = hs->GetTapNoteTypeVector(); - pss->m_vTrackVector = hs->GetTrackVector(); - } } void @@ -2055,252 +1921,6 @@ ScreenGameplay::SaveReplay() return; } -float -ScreenGameplay::SetRate(float newRate) -{ - float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - - // Rates outside of this range may crash - if (newRate < 0.3f || newRate > 5.f) - return rate; - - bool paused = GAMESTATE->GetPaused(); - - // Stop the music and generate a new "music" - m_pSoundMusic->Stop(); - - RageTimer tm; - const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); - - float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; - GetMusicEndTiming(fSecondsToStartFadingOutMusic, - fSecondsToStartTransitioningOut); - - RageSoundParams p; - p.m_StartSecond = fSeconds; - // Turns out the music rate doesn't affect anything by itself, so we have to - // set every rate - p.m_fSpeed = newRate; - GAMESTATE->m_SongOptions.GetSong().m_fMusicRate = newRate; - GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate = newRate; - GAMESTATE->m_SongOptions.GetPreferred().m_fMusicRate = newRate; - // Prevent music from making noise when doing things in pause mode - // Volume gets reset when leaving pause mode or doing almost anything else - if (paused) - p.m_Volume = 0.f; - // Set up the music so we don't wait for an Etternaty when messing around - // near the end of the song. - if (fSecondsToStartFadingOutMusic < - GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { - p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; - p.m_LengthSeconds = fSecondsToStartFadingOutMusic + - MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; - } - p.StopMode = RageSoundParams::M_CONTINUE; - - // Go - m_pSoundMusic->Play(false, &p); - // But only for like 1 frame if we are paused - if (paused) - m_pSoundMusic->Pause(true); - - // misc info update - GAMESTATE->m_Position.m_fMusicSeconds = fSeconds; - UpdateSongPosition(0); - MESSAGEMAN->Broadcast( - "CurrentRateChanged"); // Tell the theme we changed the rate - - return newRate; -} - -void -ScreenGameplay::SetSongPosition(float newPositionSeconds) -{ - // If you go too far negative, bad things may happen - // But remember some files have notes at 0.0 seconds - if (newPositionSeconds <= 0) - newPositionSeconds = 0.f; - bool paused = GAMESTATE->GetPaused(); - - // Stop the music and generate a new "music" - m_pSoundMusic->Stop(); - - RageTimer tm; - const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); - float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - - float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; - GetMusicEndTiming(fSecondsToStartFadingOutMusic, - fSecondsToStartTransitioningOut); - - // Set up current rate and new position to play - RageSoundParams p; - p.m_StartSecond = newPositionSeconds; - p.m_fSpeed = rate; - GAMESTATE->m_SongOptions.GetSong().m_fMusicRate = rate; - GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate = rate; - GAMESTATE->m_SongOptions.GetPreferred().m_fMusicRate = rate; - - // Prevent endless music or something - if (fSecondsToStartFadingOutMusic < - GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { - p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; - p.m_LengthSeconds = fSecondsToStartFadingOutMusic + - MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; - } - p.StopMode = RageSoundParams::M_CONTINUE; - - // If we scroll backwards, we need to render those notes again - if (newPositionSeconds < fSeconds) { - m_vPlayerInfo.m_pPlayer->RenderAllNotesIgnoreScores(); - } - - // If we are paused, set the volume to 0 so we don't make weird noises - if (paused) { - p.m_Volume = 0.f; - } else { - // Restart the file to make sure nothing weird is going on - ReloadCurrentSong(); - STATSMAN->m_CurStageStats.m_player.InternalInit(); - } - - // Go - m_pSoundMusic->Play(false, &p); - // But only for like 1 frame if we are paused - if (paused) - m_pSoundMusic->Pause(true); - - // misc info update - GAMESTATE->m_Position.m_fMusicSeconds = newPositionSeconds; - UpdateSongPosition(0); -} - -void -ScreenGameplay::TogglePracticePause() -{ - // True if we were paused before now - bool oldPause = GAMESTATE->GetPaused(); - // True if we are becoming paused - bool newPause = !GAMESTATE->GetPaused(); - - if (!GAMESTATE->m_pPlayerState->m_PlayerOptions.GetCurrent().m_bPractice) - return; - - if (oldPause) { - float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - m_pSoundMusic->Stop(); - - RageTimer tm; - const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); - - float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; - GetMusicEndTiming(fSecondsToStartFadingOutMusic, - fSecondsToStartTransitioningOut); - - RageSoundParams p; - p.m_StartSecond = fSeconds; - p.m_fSpeed = rate; - if (fSecondsToStartFadingOutMusic < - GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { - p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; - p.m_LengthSeconds = fSecondsToStartFadingOutMusic + - MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; - } - p.StopMode = RageSoundParams::M_CONTINUE; - // Go - m_pSoundMusic->Play(false, &p); - } else { - m_pSoundMusic->Pause(newPause); - } - GAMESTATE->SetPaused(newPause); -} - -void -ScreenGameplay::SetPracticeSongPosition(float newPositionSeconds) -{ - if (!GAMESTATE->m_pPlayerState->m_PlayerOptions.GetCurrent().m_bPractice) - return; - - m_pSoundMusic->SetPositionSeconds(newPositionSeconds); - - bool isPaused = GAMESTATE->GetPaused(); - m_pSoundMusic->Pause(isPaused); - - m_vPlayerInfo.m_pPlayer->RenderAllNotesIgnoreScores(); - - // curwifescore sent via judgment msgs is stored in player - auto pl = m_vPlayerInfo.m_pPlayer; - // but the tns counts are stored here - auto stats = m_vPlayerInfo.GetPlayerStageStats(); - - // Reset the wife/judge counter related visible stuff - // we should _probably_ reset the replaydata vectors but i don't feel like - // it if we are refactoring gameplay soon - pl->curwifescore = 0; - FOREACH_ENUM(TapNoteScore, tns) - stats->m_iTapNoteScores[tns] = 0; - FOREACH_ENUM(TapNoteScore, hns) - stats->m_iHoldNoteScores[hns] = 0; - - // just having a message we can respond to directly is probably the best way - // to reset lua elemenmts rather than emulating a judgment message like - // replays - MESSAGEMAN->Broadcast("PracticeModeReset"); -} - -float -ScreenGameplay::AddToPracticeRate(float amountAdded) -{ - float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - - if (!GAMESTATE->m_pPlayerState->m_PlayerOptions.GetCurrent().m_bPractice) - return rate; - - float newRate = rate + amountAdded; - - // Rates outside of this range may crash - // Use 0.25 because of floating point errors... - if (newRate <= 0.25f || newRate > 3.f) - return rate; - - bool paused = GAMESTATE->GetPaused(); - - m_pSoundMusic->Stop(); - - RageTimer tm; - const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); - - float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; - GetMusicEndTiming(fSecondsToStartFadingOutMusic, - fSecondsToStartTransitioningOut); - - RageSoundParams p; - p.m_StartSecond = fSeconds; - p.m_fSpeed = newRate; - GAMESTATE->m_SongOptions.GetSong().m_fMusicRate = newRate; - GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate = newRate; - GAMESTATE->m_SongOptions.GetPreferred().m_fMusicRate = newRate; - if (paused) - p.m_Volume = 0.f; - if (fSecondsToStartFadingOutMusic < - GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { - p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; - p.m_LengthSeconds = fSecondsToStartFadingOutMusic + - MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; - } - p.StopMode = RageSoundParams::M_CONTINUE; - // Go - m_pSoundMusic->Play(false, &p); - // But only for like 1 frame if we are paused - if (paused) - m_pSoundMusic->Pause(true); - GAMESTATE->m_Position.m_fMusicSeconds = fSeconds; - - MESSAGEMAN->Broadcast( - "CurrentRateChanged"); // Tell the theme we changed the rate - return newRate; -} - const float ScreenGameplay::GetSongPosition() { @@ -2309,85 +1929,6 @@ ScreenGameplay::GetSongPosition() return m_pSoundMusic->GetPositionSeconds(NULL, &tm); } -void -ScreenGameplay::ToggleReplayPause() -{ - - // True if we were paused before now - bool oldPause = GAMESTATE->GetPaused(); - // True if we are becoming paused - bool newPause = !GAMESTATE->GetPaused(); - - // We are leaving pause mode - if (oldPause) { - RageTimer tm; - - const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); - float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - - // Restart the stage, technically (This will cause a lot of lag if there - // are a lot of notes.) - ReloadCurrentSong(); - STATSMAN->m_CurStageStats.m_player.InternalInit(); - PlayerAI::SetScoreData(PlayerAI::pScoreData); - PlayerAI::SetUpExactTapMap(PlayerAI::pReplayTiming); - - // Reset the wife/judge counter related visible stuff - FOREACH_ENUM(TapNoteScore, tns) - { - Message msg = Message("Judgment"); - msg.SetParam("Judgment", tns); - msg.SetParam("WifePercent", 0); - msg.SetParam("Player", 0); - msg.SetParam("TapNoteScore", tns); - msg.SetParam("FirstTrack", 0); - msg.SetParam("CurWifeScore", 0); - msg.SetParam("MaxWifeScore", 0); - msg.SetParam("WifeDifferential", 0); - msg.SetParam("TotalPercent", 0); - msg.SetParam("Type", RString("Tap")); - msg.SetParam("Val", 0); - MESSAGEMAN->Broadcast(msg); - } - - // Set up the stage music to current params, simply - float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; - GetMusicEndTiming(fSecondsToStartFadingOutMusic, - fSecondsToStartTransitioningOut); - - RageSoundParams p; - p.m_StartSecond = fSeconds; - p.m_fSpeed = rate; - if (fSecondsToStartFadingOutMusic < - GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { - p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; - p.m_LengthSeconds = fSecondsToStartFadingOutMusic + - MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; - } - p.StopMode = RageSoundParams::M_CONTINUE; - - // Unpause - m_pSoundMusic->Play(false, &p); - GAMESTATE->m_Position.m_fMusicSeconds = fSeconds; - UpdateSongPosition(0); - // SCREENMAN->SystemMessage("Unpaused Replay"); - } else { - // Almost all of gameplay is based on the music moving. - // If the music is paused, nothing works. - // This is all we have to do. - m_pSoundMusic->Pause(newPause); - // SCREENMAN->SystemMessage("Paused Replay"); - } - GAMESTATE->SetPaused(newPause); -} - -/* -bool ScreenGameplay::LoadReplay() -{ - // Load replay which was selected via options -} -*/ - // lua start /** @brief Allow Lua to have access to the ScreenGameplay. */ @@ -2466,92 +2007,6 @@ class LunaScreenGameplay : public Luna lua_pushnumber(L, pos); return 1; } - static int SetReplayPosition(T* p, lua_State* L) - { - float newpos = FArg(1); - if (GAMESTATE->GetPaused() && - GamePreferences::m_AutoPlay == PC_REPLAY) { - p->SetSongPosition(newpos); - } - /* - else - SCREENMAN->SystemMessage( - "You must be paused to move the song position of a Replay."); - */ - return 0; - } - static int SetReplayRate(T* p, lua_State* L) - { - float newrate = FArg(1); - if (!GAMESTATE->GetPaused()) { - /* - SCREENMAN->SystemMessage( - "You must be paused to change the rate of a Replay."); - */ - lua_pushnumber(L, -1.f); - return 1; - } - if (GamePreferences::m_AutoPlay != PC_REPLAY) { - /* - SCREENMAN->SystemMessage( - "You cannot change the rate outside of a Replay."); - */ - lua_pushnumber(L, -1.f); - return 1; - } - lua_pushnumber(L, p->SetRate(newrate)); - return 1; - } - static int ToggleReplayPause(T* p, lua_State* L) - { - if (GamePreferences::m_AutoPlay != PC_REPLAY) { - /* - SCREENMAN->SystemMessage( - "You cannot pause the game outside of a Replay."); - */ - return 0; - } - p->ToggleReplayPause(); - return 0; - } - static int SetReplayBookmark(T* p, lua_State* L) - { - float position = FArg(1); - if (GamePreferences::m_AutoPlay == PC_REPLAY) { - p->m_fReplayBookmarkSeconds = position; - return 0; - } - return 0; - } - static int JumpToReplayBookmark(T* p, lua_State* L) - { - if (GamePreferences::m_AutoPlay == PC_REPLAY && - GAMESTATE->GetPaused()) { - p->SetSongPosition(p->m_fReplayBookmarkSeconds); - return 0; - } - return 0; - } - - static int SetPreviewNoteFieldMusicPosition(T* p, lua_State* L) - { - float given = FArg(1); - p->SetPracticeSongPosition(given); - return 0; - } - - static int AddToPracticeRate(T* p, lua_State* L) - { - float rate = FArg(1); - lua_pushnumber(L, p->AddToPracticeRate(rate)); - return 1; - } - - static int TogglePracticePause(T* p, lua_State* L) - { - p->TogglePracticePause(); - return 0; - } LunaScreenGameplay() { @@ -2563,49 +2018,11 @@ class LunaScreenGameplay : public Luna ADD_METHOD(GetTrueBPS); ADD_METHOD(GetSongPosition); - ADD_METHOD(SetReplayPosition); - ADD_METHOD(SetReplayRate); - ADD_METHOD(ToggleReplayPause); - ADD_METHOD(SetReplayBookmark); - ADD_METHOD(JumpToReplayBookmark); - ADD_METHOD(SetPreviewNoteFieldMusicPosition); - ADD_METHOD(AddToPracticeRate); - ADD_METHOD(TogglePracticePause); } }; LUA_REGISTER_DERIVED_CLASS(ScreenGameplay, ScreenWithMenuElements) -/** @brief Allow Lua to have access to the PlayerInfo. */ -class LunaPlayerInfo : public Luna -{ - public: - static int GetLifeMeter(T* p, lua_State* L) - { - if (p->m_pLifeMeter) { - p->m_pLifeMeter->PushSelf(L); - return 1; - } - return 0; - } - - static int GetStepsQueueWrapped(T* p, lua_State* L) - { - int iIndex = IArg(1); - iIndex %= p->m_vpStepsQueue.size(); - Steps* pSteps = p->m_vpStepsQueue[iIndex]; - pSteps->PushSelf(L); - return 1; - } - - LunaPlayerInfo() - { - ADD_METHOD(GetLifeMeter); - ADD_METHOD(GetStepsQueueWrapped); - } -}; - -LUA_REGISTER_CLASS(PlayerInfo) // lua end /* diff --git a/src/Etterna/Screen/Gameplay/ScreenGameplay.h b/src/Etterna/Screen/Gameplay/ScreenGameplay.h index 9081511319..e6d0cc530c 100644 --- a/src/Etterna/Screen/Gameplay/ScreenGameplay.h +++ b/src/Etterna/Screen/Gameplay/ScreenGameplay.h @@ -15,12 +15,9 @@ #include "Etterna/Actor/Base/Sprite.h" #include "Etterna/Models/Misc/ThemeMetric.h" #include "Etterna/Actor/GameplayAndMenus/Transition.h" +#include "Etterna/Models/Misc/PlayerInfo.h" -class LyricsLoader; -class Player; class LifeMeter; -class StepsDisplay; -class ScoreKeeper; class Background; class Foreground; @@ -28,88 +25,6 @@ AutoScreenMessage(SM_NotesEnded); AutoScreenMessage(SM_BeginFailed); AutoScreenMessage(SM_LeaveGameplay); -class PlayerInfo -{ - public: - PlayerInfo(); - ~PlayerInfo(); - - void Load(PlayerNumber pn, - MultiPlayer mp, - bool bShowNoteField, - int iAddToDifficulty); - - /** - * @brief Retrieve the player's state and stage stats index. - * @return the player's state and stage stats index. - */ - MultiPlayer GetPlayerStateAndStageStatsIndex() - { - return m_pn == PLAYER_INVALID ? m_mp : static_cast(m_pn); - } - PlayerState* GetPlayerState(); - PlayerStageStats* GetPlayerStageStats(); - PlayerNumber GetStepsAndTrailIndex() - { - return m_pn == PLAYER_INVALID ? PLAYER_1 : m_pn; - } - /** - * @brief Determine if the player information is enabled. - * @return its success or failure. */ - bool IsEnabled(); - /** - * @brief Determine if we're in MultiPlayer. - * @return true if it is MultiPlayer, false otherwise. */ - bool IsMultiPlayer() const { return m_mp != MultiPlayer_Invalid; } - /** - * @brief Retrieve the name of the Player based on the mode. - * @return the name of the Player. */ - RString GetName() const - { - if (m_bIsDummy) - return ssprintf("PlayerInfoDummy"); - if (IsMultiPlayer()) - return MultiPlayerToString(m_mp); - else - return PlayerNumberToString(m_pn); - } - - // Lua - void PushSelf(lua_State* L); - - /** @brief The present Player's number. */ - PlayerNumber m_pn; - /** @brief The present Player's multiplayer number. */ - MultiPlayer m_mp{ MultiPlayer_Invalid }; - bool m_bIsDummy{ false }; - int m_iAddToDifficulty{ 0 }; // if > 0, use the Nth harder Steps - bool m_bPlayerEnabled{ false }; // IsEnabled cache for iterators - SoundEffectControl m_SoundEffectControl; - - /** - * @brief The list of Steps a player has to go through in this set. - * - * The size may be greater than 1 if playing a course. */ - vector m_vpStepsQueue; - - /** @brief The LifeMeter showing a Player's health. */ - LifeMeter* m_pLifeMeter; - /** @brief The description of the current Steps. */ - BitmapText* m_ptextStepsDescription; - /** @brief The primary ScoreKeeper for keeping track of the score. */ - ScoreKeeper* m_pPrimaryScoreKeeper; - /** @brief The current PlayerOptions that are activated. */ - BitmapText* m_ptextPlayerOptions; - /** @brief The current attack modifiers that are in play for the moment. */ - - /** @brief The NoteData the Player has to get through. */ - NoteData m_NoteData; - /** @brief The specific Player that is going to play. */ - Player* m_pPlayer; - - StepsDisplay* m_pStepsDisplay; -}; - /** @brief The music plays, the notes scroll, and the Player is pressing * buttons. */ class ScreenGameplay : public ScreenWithMenuElements @@ -147,25 +62,8 @@ class ScreenGameplay : public ScreenWithMenuElements void FailFadeRemovePlayer(PlayerNumber pn); void BeginBackingOutFromGameplay(); - // Set the playback rate in the middle of gameplay - float SetRate(float newRate); - // Move the current position of the song in the middle of gameplay - void SetSongPosition(float newPositionSeconds); - // Set the playback rate in the middle of gameplay, in practice mode only - float AddToPracticeRate(float amountAdded); - // Move the current position of the song in the middle of gameplay, in - // practice mode only - void SetPracticeSongPosition(float newPositionSeconds); // Get current position of the song during gameplay const float GetSongPosition(); - // Toggle pause. Don't use this outside of replays. - // Please. - // I'm serious. - void ToggleReplayPause(); - // Toggle pause. It's for practice mode. - // ignore the comment above - void TogglePracticePause(); - float m_fReplayBookmarkSeconds; protected: virtual void UpdateStageStats( @@ -207,12 +105,10 @@ class ScreenGameplay : public ScreenWithMenuElements void PlayTicks(); // Used to update some pointers void UpdateSongPosition(float fDeltaTime); - void UpdateLyrics(float fDeltaTime); void SongFinished(); virtual void SaveStats(); virtual void StageFinished(bool bBackedOut); void SaveReplay(); - // bool LoadReplay(); bool AllAreFailing(); virtual void InitSongQueues(); diff --git a/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.cpp b/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.cpp index 6a83f53893..d78f4022a9 100644 --- a/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.cpp +++ b/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.cpp @@ -1,6 +1,28 @@ #include "Etterna/Globals/global.h" #include "ScreenGameplayPractice.h" #include "Etterna/Models/Misc/Difficulty.h" +#include "Etterna/Singletons/Gamestate.h" +#include "Etterna/Singletons/PrefsManager.h" +#include "Etterna/Singletons/ScreenManager.h" +#include "Etterna/Singletons/StatsManager.h" +#include "Etterna/Models/Songs/Song.h" +#include "Etterna/Actor/Gameplay/ArrowEffects.h" +#include "Etterna/Models/StepsAndStyles/Style.h" +#include "Etterna/Models/Misc/GameConstantsAndTypes.h" +#include "Etterna/Models/Misc/GamePreferences.h" +#include "Etterna/Models/Misc/Highscore.h" +#include "Etterna/Models/Misc/PlayerAI.h" +#include "Etterna/Models/Misc/PlayerInfo.h" +#include "Etterna/Models/Misc/PlayerStageStats.h" +#include "Etterna/Models/NoteData/NoteDataWithScoring.h" +#include "Etterna/Models/NoteData/NoteDataUtil.h" +#include "Etterna/Models/NoteData/NoteData.h" +#include "Etterna/Actor/Gameplay/Player.h" +#include "Etterna/Models/Misc/RadarValues.h" +#include "Etterna/Singletons/DownloadManager.h" + +#include "Etterna/Models/Lua/LuaBinding.h" +#include "Etterna/Singletons/LuaManager.h" REGISTER_SCREEN_CLASS(ScreenGameplayPractice); @@ -10,3 +32,192 @@ ScreenGameplayPractice::FillPlayerInfo(PlayerInfo* playerInfoOut) playerInfoOut->Load( PLAYER_1, MultiPlayer_Invalid, true, Difficulty_Invalid); } + +ScreenGameplayPractice::ScreenGameplayPractice() +{ + m_pSongBackground = NULL; + m_pSongForeground = NULL; + m_delaying_ready_announce = false; + GAMESTATE->m_AdjustTokensBySongCostForFinalStageCheck = false; + DLMAN->UpdateDLSpeed(true); +} + +void +ScreenGameplayPractice::Init() +{ + ScreenGameplay::Init(); +} + +ScreenGameplayPractice::~ScreenGameplayPractice() +{ + GAMESTATE->m_AdjustTokensBySongCostForFinalStageCheck = true; + if (this->IsFirstUpdate()) { + /* We never received any updates. That means we were deleted without + * being used, and never actually played. (This can happen when backing + * out of ScreenStage.) Cancel the stage. */ + GAMESTATE->CancelStage(); + } + + if (PREFSMAN->m_verbose_log > 1) + LOG->Trace("ScreenGameplayReplay::~ScreenGameplayReplay()"); + + SAFE_DELETE(m_pSongBackground); + SAFE_DELETE(m_pSongForeground); + + if (m_pSoundMusic != nullptr) + m_pSoundMusic->StopPlaying(); + + m_GameplayAssist.StopPlaying(); + + DLMAN->UpdateDLSpeed(false); +} + +void +ScreenGameplayPractice::TogglePracticePause() +{ + // True if we were paused before now + bool oldPause = GAMESTATE->GetPaused(); + // True if we are becoming paused + bool newPause = !GAMESTATE->GetPaused(); + + if (oldPause) { + float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + m_pSoundMusic->Stop(); + + RageTimer tm; + const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); + + float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; + GetMusicEndTiming(fSecondsToStartFadingOutMusic, + fSecondsToStartTransitioningOut); + + RageSoundParams p; + p.m_StartSecond = fSeconds; + p.m_fSpeed = rate; + if (fSecondsToStartFadingOutMusic < + GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { + p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; + p.m_LengthSeconds = fSecondsToStartFadingOutMusic + + MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; + } + p.StopMode = RageSoundParams::M_CONTINUE; + // Go + m_pSoundMusic->Play(false, &p); + } else { + m_pSoundMusic->Pause(newPause); + } + GAMESTATE->SetPaused(newPause); +} + +void +ScreenGameplayPractice::SetPracticeSongPosition(float newPositionSeconds) +{ + m_pSoundMusic->SetPositionSeconds(newPositionSeconds); + + bool isPaused = GAMESTATE->GetPaused(); + m_pSoundMusic->Pause(isPaused); + + m_vPlayerInfo.m_pPlayer->RenderAllNotesIgnoreScores(); + + // curwifescore sent via judgment msgs is stored in player + auto pl = m_vPlayerInfo.m_pPlayer; + // but the tns counts are stored here + auto stats = m_vPlayerInfo.GetPlayerStageStats(); + + // Reset the wife/judge counter related visible stuff + // we should _probably_ reset the replaydata vectors but i don't feel like + // it if we are refactoring gameplay soon + pl->curwifescore = 0; + FOREACH_ENUM(TapNoteScore, tns) + stats->m_iTapNoteScores[tns] = 0; + FOREACH_ENUM(TapNoteScore, hns) + stats->m_iHoldNoteScores[hns] = 0; + + // just having a message we can respond to directly is probably the best way + // to reset lua elemenmts rather than emulating a judgment message like + // replays + MESSAGEMAN->Broadcast("PracticeModeReset"); +} + +float +ScreenGameplayPractice::AddToPracticeRate(float amountAdded) +{ + float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + + float newRate = rate + amountAdded; + + // Rates outside of this range may crash + // Use 0.25 because of floating point errors... + if (newRate <= 0.25f || newRate > 3.f) + return rate; + + bool paused = GAMESTATE->GetPaused(); + + m_pSoundMusic->Stop(); + + RageTimer tm; + const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); + + float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; + GetMusicEndTiming(fSecondsToStartFadingOutMusic, + fSecondsToStartTransitioningOut); + + RageSoundParams p; + p.m_StartSecond = fSeconds; + p.m_fSpeed = newRate; + GAMESTATE->m_SongOptions.GetSong().m_fMusicRate = newRate; + GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate = newRate; + GAMESTATE->m_SongOptions.GetPreferred().m_fMusicRate = newRate; + if (paused) + p.m_Volume = 0.f; + if (fSecondsToStartFadingOutMusic < + GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { + p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; + p.m_LengthSeconds = fSecondsToStartFadingOutMusic + + MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; + } + p.StopMode = RageSoundParams::M_CONTINUE; + // Go + m_pSoundMusic->Play(false, &p); + // But only for like 1 frame if we are paused + if (paused) + m_pSoundMusic->Pause(true); + GAMESTATE->m_Position.m_fMusicSeconds = fSeconds; + + MESSAGEMAN->Broadcast( + "CurrentRateChanged"); // Tell the theme we changed the rate + return newRate; +} + +class LunaScreenGameplayPractice : public Luna +{ + public: + static int SetPreviewNoteFieldMusicPosition(T* p, lua_State* L) + { + float given = FArg(1); + p->SetPracticeSongPosition(given); + return 0; + } + + static int AddToPracticeRate(T* p, lua_State* L) + { + float rate = FArg(1); + lua_pushnumber(L, p->AddToPracticeRate(rate)); + return 1; + } + + static int TogglePracticePause(T* p, lua_State* L) + { + p->TogglePracticePause(); + return 0; + } + + LunaScreenGameplayPractice() + { + ADD_METHOD(SetPreviewNoteFieldMusicPosition); + ADD_METHOD(AddToPracticeRate); + ADD_METHOD(TogglePracticePause); + } +}; + +LUA_REGISTER_DERIVED_CLASS(ScreenGameplayPractice, ScreenGameplay) diff --git a/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.h b/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.h index f68967514c..434ba81384 100644 --- a/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.h +++ b/src/Etterna/Screen/Gameplay/ScreenGameplayPractice.h @@ -7,6 +7,32 @@ class ScreenGameplayPractice : public ScreenGameplay { public: virtual void FillPlayerInfo(PlayerInfo* playerInfoOut); + + ScreenGameplayPractice(); + void Init() override; + ~ScreenGameplayPractice() override; + + // void Update(float fDeltaTime) override; + // bool Input(const InputEventPlus& input) override; + + // Lua + void PushSelf(lua_State* L) override; + LifeMeter* GetLifeMeter(PlayerNumber pn); + PlayerInfo* GetPlayerInfo(PlayerNumber pn); + + void FailFadeRemovePlayer(PlayerInfo* pi); + void FailFadeRemovePlayer(PlayerNumber pn); + void BeginBackingOutFromGameplay(); + + // Set the playback rate in the middle of gameplay + float SetRate(float newRate); + // Set the playback rate in the middle of gameplay, in practice mode only + float AddToPracticeRate(float amountAdded); + // Move the current position of the song in the middle of gameplay, in + // practice mode only + void SetPracticeSongPosition(float newPositionSeconds); + // Toggle pause + void TogglePracticePause(); }; #endif diff --git a/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.cpp b/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.cpp index d13cd81ce7..1361a1f0e2 100644 --- a/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.cpp +++ b/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.cpp @@ -1,12 +1,482 @@ #include "Etterna/Globals/global.h" #include "ScreenGameplayReplay.h" #include "Etterna/Models/Misc/Difficulty.h" +#include "Etterna/Singletons/Gamestate.h" +#include "Etterna/Singletons/PrefsManager.h" +#include "Etterna/Singletons/ScreenManager.h" +#include "Etterna/Singletons/StatsManager.h" +#include "Etterna/Models/Songs/Song.h" +#include "Etterna/Actor/Gameplay/ArrowEffects.h" +#include "Etterna/Models/StepsAndStyles/Style.h" +#include "Etterna/Models/Misc/GameConstantsAndTypes.h" +#include "Etterna/Models/Misc/GamePreferences.h" +#include "Etterna/Models/Misc/Highscore.h" +#include "Etterna/Models/Misc/PlayerAI.h" +#include "Etterna/Models/Misc/PlayerInfo.h" +#include "Etterna/Models/Misc/PlayerStageStats.h" +#include "Etterna/Models/NoteData/NoteDataWithScoring.h" +#include "Etterna/Models/NoteData/NoteDataUtil.h" +#include "Etterna/Models/NoteData/NoteData.h" +#include "Etterna/Actor/Gameplay/Player.h" +#include "Etterna/Models/Misc/RadarValues.h" +#include "Etterna/Singletons/DownloadManager.h" + +#include "Etterna/Models/Lua/LuaBinding.h" +#include "Etterna/Singletons/LuaManager.h" REGISTER_SCREEN_CLASS(ScreenGameplayReplay); +class PlayerInfo; + void ScreenGameplayReplay::FillPlayerInfo(PlayerInfo* playerInfoOut) { playerInfoOut->Load( PLAYER_1, MultiPlayer_Invalid, true, Difficulty_Invalid); } + +ScreenGameplayReplay::ScreenGameplayReplay() +{ + m_pSongBackground = NULL; + m_pSongForeground = NULL; + m_delaying_ready_announce = false; + GAMESTATE->m_AdjustTokensBySongCostForFinalStageCheck = false; + DLMAN->UpdateDLSpeed(true); +} + +void +ScreenGameplayReplay::Init() +{ + ScreenGameplay::Init(); + + m_fReplayBookmarkSeconds = 0.f; +} + +ScreenGameplayReplay::~ScreenGameplayReplay() +{ + GAMESTATE->m_AdjustTokensBySongCostForFinalStageCheck = true; + if (this->IsFirstUpdate()) { + /* We never received any updates. That means we were deleted without + * being used, and never actually played. (This can happen when backing + * out of ScreenStage.) Cancel the stage. */ + GAMESTATE->CancelStage(); + } + + if (PREFSMAN->m_verbose_log > 1) + LOG->Trace("ScreenGameplayReplay::~ScreenGameplayReplay()"); + + SAFE_DELETE(m_pSongBackground); + SAFE_DELETE(m_pSongForeground); + + if (m_pSoundMusic != nullptr) + m_pSoundMusic->StopPlaying(); + + m_GameplayAssist.StopPlaying(); + + DLMAN->UpdateDLSpeed(false); +} + +void +ScreenGameplayReplay::Update(float fDeltaTime) +{ + if (GAMESTATE->m_pCurSong == NULL) { + Screen::Update(fDeltaTime); + return; + } + + UpdateSongPosition(fDeltaTime); + + if (m_bZeroDeltaOnNextUpdate) { + Screen::Update(0); + m_bZeroDeltaOnNextUpdate = false; + } else { + Screen::Update(fDeltaTime); + } + + if (SCREENMAN->GetTopScreen() != this) + return; + + m_AutoKeysounds.Update(fDeltaTime); + + m_vPlayerInfo.m_SoundEffectControl.Update(fDeltaTime); + + { + float fSpeed = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + RageSoundParams p = m_pSoundMusic->GetParams(); + if (std::fabs(p.m_fSpeed - fSpeed) > 0.01f && fSpeed >= 0.0f) { + p.m_fSpeed = fSpeed; + m_pSoundMusic->SetParams(p); + } + } + + switch (m_DancingState) { + case STATE_DANCING: { + PlayerNumber pn = m_vPlayerInfo.GetStepsAndTrailIndex(); + + // Update living players' alive time + // HACK: Don't scale alive time when using tab/tilde. Instead of + // accumulating time from a timer, this time should instead be tied + // to the music position. + float fUnscaledDeltaTime = m_timerGameplaySeconds.GetDeltaTime(); + if (!m_vPlayerInfo.GetPlayerStageStats()->m_bFailed) + m_vPlayerInfo.GetPlayerStageStats()->m_fAliveSeconds += + fUnscaledDeltaTime * + GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + + // update fGameplaySeconds + STATSMAN->m_CurStageStats.m_fGameplaySeconds += fUnscaledDeltaTime; + float curBeat = GAMESTATE->m_Position.m_fSongBeat; + Song& s = *GAMESTATE->m_pCurSong; + + if (curBeat >= s.GetFirstBeat() && curBeat < s.GetLastBeat()) { + STATSMAN->m_CurStageStats.m_fStepsSeconds += fUnscaledDeltaTime; + } + } + default: + break; + } + + PlayTicks(); + SendCrossedMessages(); + + // ArrowEffects::Update call moved because having it happen once per + // NoteField (which means twice in two player) seemed wasteful. -Kyz + ArrowEffects::Update(); +} + +bool +ScreenGameplayReplay::Input(const InputEventPlus& input) +{ + // LOG->Trace( "ScreenGameplayReplay::Input()" ); + + Message msg(""); + if (m_Codes.InputMessage(input, msg)) + this->HandleMessage(msg); + + if (m_DancingState != STATE_OUTRO && GAMESTATE->IsHumanPlayer(input.pn) && + !m_Cancel.IsTransitioning()) { + + // Exiting gameplay by pressing Back (Immediate Exit) + bool bHoldingBack = false; + if (GAMESTATE->GetCurrentStyle(input.pn)->GameInputToColumn( + input.GameI) == Column_Invalid) { + bHoldingBack |= input.MenuI == GAME_BUTTON_BACK; + } + + if (bHoldingBack) { + if (((!PREFSMAN->m_bDelayedBack && input.type == IET_FIRST_PRESS) || + (input.DeviceI.device == DEVICE_KEYBOARD && + input.type == IET_REPEAT) || + (input.DeviceI.device != DEVICE_KEYBOARD && + INPUTFILTER->GetSecsHeld(input.DeviceI) >= 1.0f))) { + if (PREFSMAN->m_verbose_log > 1) + LOG->Trace("Player %i went back", input.pn + 1); + BeginBackingOutFromGameplay(); + } else if (PREFSMAN->m_bDelayedBack && + input.type == IET_FIRST_PRESS) { + m_textDebug.SetText(GIVE_UP_BACK_TEXT); + m_textDebug.PlayCommand("BackOn"); + } else if (PREFSMAN->m_bDelayedBack && input.type == IET_RELEASE) { + m_textDebug.PlayCommand("TweenOff"); + } + + return true; + } + } + + if (!input.GameI.IsValid()) + return false; + + /* Restart gameplay button moved from theme to allow for rebinding for + * people who dont want to edit lua files :) + */ + bool bHoldingRestart = false; + if (GAMESTATE->GetCurrentStyle(input.pn)->GameInputToColumn(input.GameI) == + Column_Invalid) { + bHoldingRestart |= input.MenuI == GAME_BUTTON_RESTART; + } + if (bHoldingRestart) { + SCREENMAN->GetTopScreen()->SetPrevScreenName("ScreenStageInformation"); + BeginBackingOutFromGameplay(); + } + + return false; +} + +void +ScreenGameplayReplay::SaveStats() +{ + // We need to replace the newly created replay data with the actual old + // data Because to keep consistently lazy practices, we can just hack + // things together instead of fixing the real issue -poco + // (doing this fixes a lot of issues in the eval screen) + PlayerStageStats* pss = m_vPlayerInfo.GetPlayerStageStats(); + HighScore* hs = PlayerAI::pScoreData; + pss->m_vHoldReplayData = hs->GetHoldReplayDataVector(); + pss->m_vNoteRowVector = hs->GetNoteRowVector(); + pss->m_vOffsetVector = hs->GetOffsetVector(); + pss->m_vTapNoteTypeVector = hs->GetTapNoteTypeVector(); + pss->m_vTrackVector = hs->GetTrackVector(); + + ScreenGameplay::SaveStats(); +} + +void +ScreenGameplayReplay::StageFinished(bool bBackedOut) +{ + if (bBackedOut) { + GAMESTATE->CancelStage(); + return; + } + + STATSMAN->m_CurStageStats.FinalizeScores(false); + + STATSMAN->m_vPlayedStageStats.push_back(STATSMAN->m_CurStageStats); + + STATSMAN->CalcAccumPlayedStageStats(); + GAMESTATE->FinishStage(); +} + +float +ScreenGameplayReplay::SetRate(float newRate) +{ + float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + + // Rates outside of this range may crash + if (newRate < 0.3f || newRate > 5.f) + return rate; + + bool paused = GAMESTATE->GetPaused(); + + // Stop the music and generate a new "music" + m_pSoundMusic->Stop(); + + RageTimer tm; + const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); + + float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; + GetMusicEndTiming(fSecondsToStartFadingOutMusic, + fSecondsToStartTransitioningOut); + + RageSoundParams p; + p.m_StartSecond = fSeconds; + // Turns out the music rate doesn't affect anything by itself, so we have to + // set every rate + p.m_fSpeed = newRate; + GAMESTATE->m_SongOptions.GetSong().m_fMusicRate = newRate; + GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate = newRate; + GAMESTATE->m_SongOptions.GetPreferred().m_fMusicRate = newRate; + // Prevent music from making noise when doing things in pause mode + // Volume gets reset when leaving pause mode or doing almost anything else + if (paused) + p.m_Volume = 0.f; + // Set up the music so we don't wait for an Etternaty when messing around + // near the end of the song. + if (fSecondsToStartFadingOutMusic < + GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { + p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; + p.m_LengthSeconds = fSecondsToStartFadingOutMusic + + MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; + } + p.StopMode = RageSoundParams::M_CONTINUE; + + // Go + m_pSoundMusic->Play(false, &p); + // But only for like 1 frame if we are paused + if (paused) + m_pSoundMusic->Pause(true); + + // misc info update + GAMESTATE->m_Position.m_fMusicSeconds = fSeconds; + UpdateSongPosition(0); + MESSAGEMAN->Broadcast( + "CurrentRateChanged"); // Tell the theme we changed the rate + + return newRate; +} + +void +ScreenGameplayReplay::SetSongPosition(float newPositionSeconds) +{ + // If you go too far negative, bad things may happen + // But remember some files have notes at 0.0 seconds + if (newPositionSeconds <= 0) + newPositionSeconds = 0.f; + bool paused = GAMESTATE->GetPaused(); + + // Stop the music and generate a new "music" + m_pSoundMusic->Stop(); + + RageTimer tm; + const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); + float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + + float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; + GetMusicEndTiming(fSecondsToStartFadingOutMusic, + fSecondsToStartTransitioningOut); + + // Set up current rate and new position to play + RageSoundParams p; + p.m_StartSecond = newPositionSeconds; + p.m_fSpeed = rate; + GAMESTATE->m_SongOptions.GetSong().m_fMusicRate = rate; + GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate = rate; + GAMESTATE->m_SongOptions.GetPreferred().m_fMusicRate = rate; + + // Prevent endless music or something + if (fSecondsToStartFadingOutMusic < + GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { + p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; + p.m_LengthSeconds = fSecondsToStartFadingOutMusic + + MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; + } + p.StopMode = RageSoundParams::M_CONTINUE; + + // If we scroll backwards, we need to render those notes again + if (newPositionSeconds < fSeconds) { + m_vPlayerInfo.m_pPlayer->RenderAllNotesIgnoreScores(); + } + + // If we are paused, set the volume to 0 so we don't make weird noises + if (paused) { + p.m_Volume = 0.f; + } else { + // Restart the file to make sure nothing weird is going on + ReloadCurrentSong(); + STATSMAN->m_CurStageStats.m_player.InternalInit(); + } + + // Go + m_pSoundMusic->Play(false, &p); + // But only for like 1 frame if we are paused + if (paused) + m_pSoundMusic->Pause(true); + + // misc info update + GAMESTATE->m_Position.m_fMusicSeconds = newPositionSeconds; + UpdateSongPosition(0); +} + +void +ScreenGameplayReplay::ToggleReplayPause() +{ + // True if we were paused before now + bool oldPause = GAMESTATE->GetPaused(); + // True if we are becoming paused + bool newPause = !GAMESTATE->GetPaused(); + + // We are leaving pause mode + if (oldPause) { + RageTimer tm; + + const float fSeconds = m_pSoundMusic->GetPositionSeconds(NULL, &tm); + float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + + // Restart the stage, technically (This will cause a lot of lag if there + // are a lot of notes.) + ReloadCurrentSong(); + STATSMAN->m_CurStageStats.m_player.InternalInit(); + PlayerAI::SetScoreData(PlayerAI::pScoreData); + PlayerAI::SetUpExactTapMap(PlayerAI::pReplayTiming); + + // Reset the wife/judge counter related visible stuff + FOREACH_ENUM(TapNoteScore, tns) + { + Message msg = Message("Judgment"); + msg.SetParam("Judgment", tns); + msg.SetParam("WifePercent", 0); + msg.SetParam("Player", 0); + msg.SetParam("TapNoteScore", tns); + msg.SetParam("FirstTrack", 0); + msg.SetParam("CurWifeScore", 0); + msg.SetParam("MaxWifeScore", 0); + msg.SetParam("WifeDifferential", 0); + msg.SetParam("TotalPercent", 0); + msg.SetParam("Type", RString("Tap")); + msg.SetParam("Val", 0); + MESSAGEMAN->Broadcast(msg); + } + + // Set up the stage music to current params, simply + float fSecondsToStartFadingOutMusic, fSecondsToStartTransitioningOut; + GetMusicEndTiming(fSecondsToStartFadingOutMusic, + fSecondsToStartTransitioningOut); + + RageSoundParams p; + p.m_StartSecond = fSeconds; + p.m_fSpeed = rate; + if (fSecondsToStartFadingOutMusic < + GAMESTATE->m_pCurSong->m_fMusicLengthSeconds) { + p.m_fFadeOutSeconds = MUSIC_FADE_OUT_SECONDS; + p.m_LengthSeconds = fSecondsToStartFadingOutMusic + + MUSIC_FADE_OUT_SECONDS - p.m_StartSecond; + } + p.StopMode = RageSoundParams::M_CONTINUE; + + // Unpause + m_pSoundMusic->Play(false, &p); + GAMESTATE->m_Position.m_fMusicSeconds = fSeconds; + UpdateSongPosition(0); + // SCREENMAN->SystemMessage("Unpaused Replay"); + } else { + // Almost all of gameplay is based on the music moving. + // If the music is paused, nothing works. + // This is all we have to do. + m_pSoundMusic->Pause(newPause); + // SCREENMAN->SystemMessage("Paused Replay"); + } + GAMESTATE->SetPaused(newPause); +} + +// lua +class LunaScreenGameplayReplay : public Luna +{ + public: + static int SetReplayPosition(T* p, lua_State* L) + { + float newpos = FArg(1); + if (GAMESTATE->GetPaused()) + p->SetSongPosition(newpos); + return 0; + } + static int SetReplayRate(T* p, lua_State* L) + { + float newrate = FArg(1); + if (!GAMESTATE->GetPaused()) { + lua_pushnumber(L, -1.f); + return 1; + } + lua_pushnumber(L, p->SetRate(newrate)); + return 1; + } + static int ToggleReplayPause(T* p, lua_State* L) + { + p->ToggleReplayPause(); + return 0; + } + static int SetReplayBookmark(T* p, lua_State* L) + { + float position = FArg(1); + p->m_fReplayBookmarkSeconds = position; + return 0; + } + static int JumpToReplayBookmark(T* p, lua_State* L) + { + if (GAMESTATE->GetPaused()) { + p->SetSongPosition(p->m_fReplayBookmarkSeconds); + return 0; + } + return 0; + } + + LunaScreenGameplayReplay() + { + ADD_METHOD(SetReplayPosition); + ADD_METHOD(SetReplayRate); + ADD_METHOD(ToggleReplayPause); + ADD_METHOD(SetReplayBookmark); + ADD_METHOD(JumpToReplayBookmark); + } +}; + +LUA_REGISTER_DERIVED_CLASS(ScreenGameplayReplay, ScreenGameplay) diff --git a/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.h b/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.h index 529e20ec4c..cb8f5f5d31 100644 --- a/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.h +++ b/src/Etterna/Screen/Gameplay/ScreenGameplayReplay.h @@ -7,6 +7,30 @@ class ScreenGameplayReplay : public ScreenGameplay { public: virtual void FillPlayerInfo(PlayerInfo* playerInfoOut); + ScreenGameplayReplay(); + void Init() override; + ~ScreenGameplayReplay() override; + + void Update(float fDeltaTime) override; + bool Input(const InputEventPlus& input) override; + + // Lua + void PushSelf(lua_State* L) override; + PlayerInfo* GetPlayerInfo(PlayerNumber pn); + + // void BeginBackingOutFromGameplay(); + + // Set the playback rate in the middle of gameplay + float SetRate(float newRate); + // Move the current position of the song in the middle of gameplay + void SetSongPosition(float newPositionSeconds); + // Toggle pause + void ToggleReplayPause(); + float m_fReplayBookmarkSeconds; + + protected: + void SaveStats() override; + void StageFinished(bool bBackedOut) override; }; #endif