From 4be5c99b50eb78079fc9b9a6a24ce1c74cab41ae Mon Sep 17 00:00:00 2001 From: Barinade Date: Wed, 7 Sep 2022 23:03:12 -0500 Subject: [PATCH] properly treat dropped holds/rolls --- src/Etterna/Actor/Gameplay/PlayerReplay.cpp | 61 +++++++++++++-------- src/Etterna/Actor/Gameplay/PlayerReplay.h | 7 +++ src/Etterna/Models/HighScore/Replay.cpp | 34 ++++++++++++ src/Etterna/Models/HighScore/Replay.h | 10 ++++ 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/Etterna/Actor/Gameplay/PlayerReplay.cpp b/src/Etterna/Actor/Gameplay/PlayerReplay.cpp index dc6d65561e..95ccebe6ca 100644 --- a/src/Etterna/Actor/Gameplay/PlayerReplay.cpp +++ b/src/Etterna/Actor/Gameplay/PlayerReplay.cpp @@ -85,6 +85,7 @@ PlayerReplay::Load() if (replay != nullptr) { SetPlaybackEvents(replay->GeneratePlaybackEvents()); + SetDroppedHolds(replay->GenerateDroppedHoldColumnsToRowsMap()); // the above replay pointer is temporary // drop the refcount @@ -120,31 +121,47 @@ PlayerReplay::UpdateHoldNotes(int iSongRow, ASSERT(iFirstTrackWithMaxEndRow != -1); + // drop holds which should be dropped for (auto& trtn : vTN) { - // 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? - auto holdDropped = false; - for (auto yeet = vTN[0].iRow; yeet <= iSongRow && !holdDropped; - yeet++) { - if (PlayerAI::DetermineIfHoldDropped(yeet, trtn.iTrack)) { - holdDropped = true; + + // find dropped holds in this column + if (droppedHolds.count(trtn.iTrack) != 0) { + + // start no earlier than the top of the current hold + // stop when the current row is reached or end of drops is reached + // this loop behavior implies you CAN drop a single hold more than once. + // that would be incorrect behavior because you cant score a note twice + // but ... account for impossible scenarios anyways + for (auto it = droppedHolds.at(trtn.iTrack).lower_bound(trtn.iRow); + it != droppedHolds.at(trtn.iTrack).end() && *it <= iSongRow; + it = droppedHolds.at(trtn.iTrack).lower_bound(*it + 1)) { + + // allow the game to treat a missed hold head + // on its own and not force an extra drop + // (check for None is for miniholds) + if (trtn.pTN->result.tns != TNS_Miss && + trtn.pTN->result.tns != TNS_None) { + trtn.pTN->HoldResult.bHeld = false; + trtn.pTN->HoldResult.bActive = false; + trtn.pTN->HoldResult.fLife = 0.f; + trtn.pTN->HoldResult.hns = HNS_LetGo; + + // score the dead hold + if (COMBO_BREAK_ON_IMMEDIATE_HOLD_LET_GO) { + IncrementMissCombo(); + } + SetHoldJudgment( + *trtn.pTN, iFirstTrackWithMaxEndRow, iSongRow); + HandleHoldScore(*trtn.pTN); + } + + droppedHolds.at(trtn.iTrack).erase(*it); } - } - if (holdDropped) // it should be dead - { - trtn.pTN->HoldResult.bHeld = false; - trtn.pTN->HoldResult.bActive = false; - trtn.pTN->HoldResult.fLife = 0.f; - trtn.pTN->HoldResult.hns = HNS_LetGo; - - // score the dead hold - if (COMBO_BREAK_ON_IMMEDIATE_HOLD_LET_GO) - IncrementMissCombo(); - SetHoldJudgment(*trtn.pTN, iFirstTrackWithMaxEndRow, iSongRow); - HandleHoldScore(*trtn.pTN); - return; + // remove the empty column to optimize this on next iteration + if (droppedHolds.at(trtn.iTrack).size() == 0) { + droppedHolds.erase(trtn.iTrack); + } } } } diff --git a/src/Etterna/Actor/Gameplay/PlayerReplay.h b/src/Etterna/Actor/Gameplay/PlayerReplay.h index 80a8d87ef0..737618496c 100644 --- a/src/Etterna/Actor/Gameplay/PlayerReplay.h +++ b/src/Etterna/Actor/Gameplay/PlayerReplay.h @@ -37,6 +37,12 @@ class PlayerReplay : public Player void SetPlaybackEvents(const std::map>& v) { playbackEvents = v; } + std::map>& GetDroppedHolds() { + return droppedHolds; + } + void SetDroppedHolds(const std::map>& v) { + droppedHolds = v; + } protected: void UpdateHoldsAndRolls( @@ -48,6 +54,7 @@ class PlayerReplay : public Player void CheckForSteps(const std::chrono::steady_clock::time_point& tm); std::map> playbackEvents{}; + std::map> droppedHolds{}; std::set holdingColumns{}; }; diff --git a/src/Etterna/Models/HighScore/Replay.cpp b/src/Etterna/Models/HighScore/Replay.cpp index 06a3c3d5fd..5d3ba09800 100644 --- a/src/Etterna/Models/HighScore/Replay.cpp +++ b/src/Etterna/Models/HighScore/Replay.cpp @@ -774,6 +774,10 @@ Replay::ValidateOffsets() auto Replay::GenerateInputData() -> bool { + if (!InputData.empty()) { + return true; + } + if (!LoadReplayData() && !LoadInputData() && !GenerateNoterowsFromTimestamps()) { Locator::getLogger()->warn("Failed to generate input data because " "replay for score {} could not be loaded", @@ -956,6 +960,36 @@ Replay::GeneratePlaybackEvents() -> std::map> return out; } +auto +Replay::GenerateDroppedHoldColumnsToRowsMap() -> std::map> +{ + std::map> mapping; + + for (auto& h : vHoldReplayDataVector) { + if (mapping.count(h.track) == 0) { + mapping.emplace(h.track, std::set()); + } + mapping.at(h.track).insert(h.row); + } + + return mapping; +} + +auto +Replay::GenerateDroppedHoldRowsToColumnsMap() -> std::map> +{ + std::map> mapping; + + for (auto& h : vHoldReplayDataVector) { + if (mapping.count(h.row) == 0) { + mapping.emplace(h.row, std::set()); + } + mapping.at(h.row).insert(h.track); + } + + return mapping; +} + // Lua #include "Etterna/Models/Lua/LuaBinding.h" diff --git a/src/Etterna/Models/HighScore/Replay.h b/src/Etterna/Models/HighScore/Replay.h index 8455e9db2e..2906c50ca9 100644 --- a/src/Etterna/Models/HighScore/Replay.h +++ b/src/Etterna/Models/HighScore/Replay.h @@ -3,6 +3,7 @@ #include "Etterna/Models/Misc/EnumHelper.h" #include "ReplayConstantsAndTypes.h" +#include struct HighScore; @@ -172,6 +173,15 @@ class Replay auto GenerateInputData() -> bool; auto GeneratePlaybackEvents() -> std::map>; + // Instead of making some complex iterator... + // Just offer both solutions + /// Returns map of columns to a set of rows which are dropped + /// See which columns have drops using this + auto GenerateDroppedHoldColumnsToRowsMap() -> std::map>; + /// Returns a map of rows to a set of columns which are dropped + /// See which rows have drops using this + auto GenerateDroppedHoldRowsToColumnsMap() -> std::map>; + // Offsets can be really weird - Remove all impossible offsets inline void ValidateOffsets();