diff --git a/src/NoteTypes.h b/src/NoteTypes.h index d896dfbfdf..650d76d385 100644 --- a/src/NoteTypes.h +++ b/src/NoteTypes.h @@ -209,6 +209,13 @@ struct HoldReplayResult { TapNoteSubType subType; }; +struct TapReplayResult { + int row; + int track; // column + float offset; // 0 + TapNoteType type; //typically mines, holds, rolls, etc +}; + extern TapNote TAP_EMPTY; // '0' extern TapNote TAP_ORIGINAL_TAP; // '1' extern TapNote TAP_ORIGINAL_HOLD_HEAD; // '2' diff --git a/src/Player.cpp b/src/Player.cpp index b928f7606f..4ab35fe0c9 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -39,6 +39,7 @@ #include "NoteSkinManager.h" #include "ThemeMetric.h" #include "HoldJudgment.h" +#include "GamePreferences.h" RString ATTACK_DISPLAY_X_NAME( size_t p, size_t both_sides ); void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ); @@ -864,7 +865,7 @@ void Player::Update( float fDeltaTime ) if( tn.HoldResult.fLife >= 0.5f ) continue; - Step( iTrack, iHeadRow, now, false, false ); + Step( iTrack, iHeadRow, now, true, false ); // bHeld really doesnt make a difference for autoplay and replay if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY) { STATSMAN->m_CurStageStats.m_bUsedAutoplay = true; @@ -1049,6 +1050,41 @@ void Player::UpdateHoldNotes( int iSongRow, float fDeltaTime, vectorpTN; + + // check from now until the head of the hold to see if it should die + // possibly really bad, but we dont REALLY care that much about fps in replays, right? + bool holdDropped = false; + for (int yeet = vTN[0].iRow; yeet <= iSongRow && !holdDropped; yeet++) + { + if (PlayerAI::DetermineIfHoldDropped(yeet, trtn->iTrack)) + { + holdDropped = true; + } + } + + if (holdDropped) // it should be dead + { + tn.HoldResult.bHeld = false; + tn.HoldResult.bActive = false; + tn.HoldResult.fLife = 0.f; + tn.HoldResult.hns = HNS_LetGo; + + // score the dead hold + if (COMBO_BREAK_ON_IMMEDIATE_HOLD_LET_GO) + IncrementMissCombo(); + SetHoldJudgment(tn, iFirstTrackWithMaxEndRow, iSongRow); + HandleHoldScore(tn); + return; + } + } + } + + //LOG->Trace("hold note doesn't already have result, let's check."); //LOG->Trace( ssprintf("[C++] hold note score: %s",HoldNoteScoreToString(hns).c_str()) ); @@ -2140,25 +2176,31 @@ void Player::Step( int col, int row, const std::chrono::steady_clock::time_point break; case PC_REPLAY: - - fNoteOffset = PlayerAI::GetTapNoteOffsetForReplay(pTN, iRowOfOverlappingNoteOrRow, col); - - if (fNoteOffset == -2.f) - { - CHECKPOINT_M("mine hit"); - score = TNS_HitMine; - } - else if (pTN->type == TapNoteType_Mine) + + if (bHeld) //a hack to make Rolls not do weird things like count as 0ms marvs. { - return; + score = TNS_None; + fNoteOffset = -1.f; } else { - if ( pTN->IsNote() ) - score = PlayerAI::GetTapNoteScoreForReplay(m_pPlayerState, fNoteOffset); + fNoteOffset = PlayerAI::GetTapNoteOffsetForReplay(pTN, iRowOfOverlappingNoteOrRow, col); + if (fNoteOffset == -2.f) // we hit a mine + { + score = TNS_HitMine; + } + else if (pTN->type == TapNoteType_Mine) // we are looking at a mine but missed it + { + return; + } + else // every other case + { + if (pTN->IsNote()) + score = PlayerAI::GetTapNoteScoreForReplay(m_pPlayerState, fNoteOffset); + } } - + break; default: FAIL_M(ssprintf("Invalid player controller type: %i", m_pPlayerState->m_PlayerController)); @@ -2197,8 +2239,15 @@ void Player::Step( int col, int row, const std::chrono::steady_clock::time_point if ( pTN->result.tns != TNS_None ) { - SetJudgment(iRowOfOverlappingNoteOrRow, col, *pTN); - HandleTapRowScore(iRowOfOverlappingNoteOrRow); + if (pTN->type == TapNoteType_HoldHead && m_pPlayerState->m_PlayerController == PC_REPLAY && bHeld) + { + // odd hack to make roll taps (Step() with bHeld true) not count as marvs + } + else + { + SetJudgment(iRowOfOverlappingNoteOrRow, col, *pTN); + HandleTapRowScore(iRowOfOverlappingNoteOrRow); + } } } } @@ -2243,11 +2292,22 @@ void Player::Step( int col, int row, const std::chrono::steady_clock::time_point } } // XXX: + if( !bRelease ) { if( m_pNoteField != nullptr ) - { - m_pNoteField->Step( col, score ); + { // skip tapping in replay mode on misses to emulate... missing. + if (m_pPlayerState->m_PlayerController == PC_REPLAY) + { + if (score != TNS_Miss) + { + m_pNoteField->Step(col, score); + } + } + else + { + m_pNoteField->Step(col, score); + } } Message msg( "Step" ); msg.SetParam( "PlayerNumber", m_pPlayerState->m_PlayerNumber ); diff --git a/src/PlayerAI.cpp b/src/PlayerAI.cpp index d24a8e88be..f1b18fcfc4 100644 --- a/src/PlayerAI.cpp +++ b/src/PlayerAI.cpp @@ -53,6 +53,8 @@ struct TapScoreDistribution static TapScoreDistribution g_Distributions[NUM_SKILL_LEVELS]; HighScore* PlayerAI::pScoreData = nullptr; +map> PlayerAI::m_ReplayTapMap; +map> PlayerAI::m_ReplayHoldMap; void PlayerAI::InitFromDisk() { @@ -123,9 +125,14 @@ TapNoteScore PlayerAI::GetTapNoteScore( const PlayerState* pPlayerState ) TapNoteScore PlayerAI::GetTapNoteScoreForReplay(const PlayerState* pPlayerState, float fNoteOffset) { + // This code is basically a copy paste from somewhere in Player for grabbing scores. + //LOG->Trace("Given number %f ", fNoteOffset); + if (fNoteOffset <= -1.0f) + return TNS_Miss; const float fSecondsFromExact = fabsf(fNoteOffset); //LOG->Trace("TapNoteScore For Replay Seconds From Exact: %f", fSecondsFromExact); + if (fSecondsFromExact <= Player::GetWindowSeconds(TW_W1)) return TNS_W1; else if (fSecondsFromExact <= Player::GetWindowSeconds(TW_W2)) @@ -142,7 +149,69 @@ TapNoteScore PlayerAI::GetTapNoteScoreForReplay(const PlayerState* pPlayerState, void PlayerAI::SetScoreData(HighScore* pHighScore) { pHighScore->LoadReplayData(); - PlayerAI::pScoreData = pHighScore; + pScoreData = pHighScore; + auto replayNoteRowVector = pHighScore->GetCopyOfNoteRowVector(); + auto replayOffsetVector = pHighScore->GetCopyOfOffsetVector(); + auto replayTapNoteTypeVector = pHighScore->GetCopyOfTapNoteTypeVector(); + auto replayTrackVector = pHighScore->GetCopyOfTrackVector(); + auto replayHoldVector = pHighScore->GetCopyOfHoldReplayDataVector(); + + // Generate TapReplayResults to put into a vector referenced by the song row in a map + for (int i = 0; i < replayTrackVector.size(); i++) + { + TapReplayResult trr; + trr.row = replayNoteRowVector[i]; + trr.track = replayTrackVector[i]; + trr.offset = replayOffsetVector[i]; + trr.type = replayTapNoteTypeVector[i]; + + // Create or append to the vector + if (m_ReplayTapMap.count(replayNoteRowVector[i]) != 0) + { + m_ReplayTapMap[replayNoteRowVector[i]].push_back(trr); + } + else + { + vector trrVector = { trr }; + m_ReplayTapMap[replayNoteRowVector[i]] = trrVector; + } + } + + // Generate vectors made of pregenerated HoldReplayResults referenced by the song row in a map + for (int i = 0; i < replayHoldVector.size(); i++) + { + // Create or append to the vector + if (m_ReplayHoldMap.count(replayHoldVector[i].row) != 0) + { + m_ReplayHoldMap[replayHoldVector[i].row].push_back(replayHoldVector[i]); + } + else + { + vector hrrVector = { replayHoldVector[i] }; + m_ReplayHoldMap[replayHoldVector[i].row] = hrrVector; + } + } +} + +bool PlayerAI::DetermineIfHoldDropped(int noteRow, int col) +{ + //LOG->Trace("Checking for hold."); + // Is the given row/column in our dropped hold map? + if (m_ReplayHoldMap.count(noteRow) != 0) + { + //LOG->Trace("Hold row exists in the data"); + // It is, so let's go over each column, assuming we may have dropped more than one hold at once. + for (auto hrr : m_ReplayHoldMap[noteRow]) + { + // We found the column we are looking for + if (hrr.track == col) + { + //LOG->Trace("KILL IT NOW"); + return true; + } + } + } + return false; } float PlayerAI::GetTapNoteOffsetForReplay(TapNote* pTN, int noteRow, int col) @@ -151,57 +220,25 @@ float PlayerAI::GetTapNoteOffsetForReplay(TapNote* pTN, int noteRow, int col) If it is not found, it is a miss. (1.f) */ if (pScoreData == nullptr) // possible cheat prevention - return 1.f; + return -1.f; - // Replay Data format: [noterow] [offset] [track] [optional: tap note type] + // Current v0.60 Replay Data format: [noterow] [offset] [track] [optional: tap note type] + // Current v0.60 Replay Data format (H section): H [noterow] [track] [optional: tap note subtype] - vector noteRowVector = pScoreData->GetCopyOfNoteRowVector(); - vector offsetVector = pScoreData->GetCopyOfOffsetVector(); - vector tntVector = pScoreData->GetCopyOfTapNoteTypeVector(); - vector trackVector = pScoreData->GetCopyOfTrackVector(); - /*std::string s = std::to_string(noteRow); - char const* nr1 = s.c_str(); - std::string lmao = std::to_string(noteRowVector.size()); - char const* nrsize = lmao.c_str(); - LOG->Trace("vector size %s", nrsize); - - LOG->Trace("Comparing %s", nr1);*/ - - for (int i = 0; i < noteRowVector.size(); i++) + if (m_ReplayTapMap.count(noteRow) != 0) // is the current row recorded? { - /*std::string g = std::to_string(i); - char const* yeet1 = g.c_str(); - LOG->Trace(yeet1);*/ - if (noteRowVector[i] == noteRow) + for (auto trr : m_ReplayTapMap[noteRow]) // go over all elements in the row { - //std::string outp = std::to_string(offsetVector[i]); - float outputF = offsetVector[i]; - //char const* output = outp.c_str(); - - if (tntVector.size() > i && tntVector[i] == TapNoteType_Mine) + if (trr.track == col) // if the column expected is the actual note, use it { - outputF = 2.f; + if (trr.type == TapNoteType_Mine) // hack for mines + return -2.f; + return -trr.offset; } - else - { - pTN->result.fTapNoteOffset = outputF; - } - - noteRowVector.erase(noteRowVector.begin() + i); - offsetVector.erase(offsetVector.begin() + i); - if (tntVector.size() > 0) { - trackVector.erase(trackVector.begin() + i); - tntVector.erase(tntVector.begin() + i); - pScoreData->SetTrackVector(trackVector); - pScoreData->SetTapNoteTypeVector(tntVector); - } - pScoreData->SetNoteRowVector(noteRowVector); - pScoreData->SetOffsetVector(offsetVector); - //LOG->Trace("returned number %s", output); - return -outputF; } } - return 0.f; + + return -1.f; // data missing or invalid, give them a miss } /* diff --git a/src/PlayerAI.h b/src/PlayerAI.h index 6aea0b3f68..a3e2d4d4ea 100644 --- a/src/PlayerAI.h +++ b/src/PlayerAI.h @@ -13,13 +13,25 @@ const int NUM_SKILL_LEVELS = 6; // 0-5 class PlayerAI { public: + // Pointer to real high score data for a replay + static HighScore* pScoreData; + // Pulled from pScoreData on initialization + + // A map with indices for each row of the chart, pointing to nothing or a Normal Result + static map> m_ReplayTapMap; + // A map with indices for each row of the chart, pointing to nothing or hold drop results. + static map> m_ReplayHoldMap; + static void InitFromDisk(); static TapNoteScore GetTapNoteScore( const PlayerState* pPlayerState ); static void SetScoreData(HighScore* pHighScore); + static float GetTapNoteOffsetForReplay(TapNote* pTN, int noteRow, int col); static TapNoteScore GetTapNoteScoreForReplay(const PlayerState* pPlayerState, float fNoteOffset); + static bool DetermineIfHoldDropped(int noteRow, int col); + }; diff --git a/src/ScreenEvaluation.cpp b/src/ScreenEvaluation.cpp index 3a8fbaaa43..58583d08ad 100644 --- a/src/ScreenEvaluation.cpp +++ b/src/ScreenEvaluation.cpp @@ -674,10 +674,7 @@ void ScreenEvaluation::Init() default: break; } - if (GamePreferences::m_AutoPlay == PC_REPLAY) - { - STATSMAN->m_vPlayedStageStats.pop_back(); - } + } bool ScreenEvaluation::Input( const InputEventPlus &input ) diff --git a/src/ScreenGameplay.cpp b/src/ScreenGameplay.cpp index 4c43b8d3eb..43aa33e6f6 100644 --- a/src/ScreenGameplay.cpp +++ b/src/ScreenGameplay.cpp @@ -2044,8 +2044,8 @@ void ScreenGameplay::StageFinished( bool bBackedOut ) FOREACH_HumanPlayer( pn ) STATSMAN->m_CurStageStats.m_player[pn].CalcAwards( pn, STATSMAN->m_CurStageStats.m_bGaveUp, STATSMAN->m_CurStageStats.m_bUsedAutoplay ); STATSMAN->m_CurStageStats.FinalizeScores( false ); - GAMESTATE->CommitStageStats(); - + if ( GamePreferences::m_AutoPlay == PC_HUMAN ) + GAMESTATE->CommitStageStats(); // save current stage stats STATSMAN->m_vPlayedStageStats.push_back( STATSMAN->m_CurStageStats );