Skip to content

Commit

Permalink
properly treat dropped holds/rolls
Browse files Browse the repository at this point in the history
  • Loading branch information
poco0317 committed Sep 8, 2022
1 parent 86a70a0 commit 4be5c99
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 22 deletions.
61 changes: 39 additions & 22 deletions src/Etterna/Actor/Gameplay/PlayerReplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ PlayerReplay::Load()

if (replay != nullptr) {
SetPlaybackEvents(replay->GeneratePlaybackEvents());
SetDroppedHolds(replay->GenerateDroppedHoldColumnsToRowsMap());

// the above replay pointer is temporary
// drop the refcount
Expand Down Expand Up @@ -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);
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Etterna/Actor/Gameplay/PlayerReplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class PlayerReplay : public Player
void SetPlaybackEvents(const std::map<int, std::vector<PlaybackEvent>>& v) {
playbackEvents = v;
}
std::map<int, std::set<int>>& GetDroppedHolds() {
return droppedHolds;
}
void SetDroppedHolds(const std::map<int, std::set<int>>& v) {
droppedHolds = v;
}

protected:
void UpdateHoldsAndRolls(
Expand All @@ -48,6 +54,7 @@ class PlayerReplay : public Player
void CheckForSteps(const std::chrono::steady_clock::time_point& tm);

std::map<int, std::vector<PlaybackEvent>> playbackEvents{};
std::map<int, std::set<int>> droppedHolds{};
std::set<int> holdingColumns{};
};

Expand Down
34 changes: 34 additions & 0 deletions src/Etterna/Models/HighScore/Replay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -956,6 +960,36 @@ Replay::GeneratePlaybackEvents() -> std::map<int, std::vector<PlaybackEvent>>
return out;
}

auto
Replay::GenerateDroppedHoldColumnsToRowsMap() -> std::map<int, std::set<int>>
{
std::map<int, std::set<int>> mapping;

for (auto& h : vHoldReplayDataVector) {
if (mapping.count(h.track) == 0) {
mapping.emplace(h.track, std::set<int>());
}
mapping.at(h.track).insert(h.row);
}

return mapping;
}

auto
Replay::GenerateDroppedHoldRowsToColumnsMap() -> std::map<int, std::set<int>>
{
std::map<int, std::set<int>> mapping;

for (auto& h : vHoldReplayDataVector) {
if (mapping.count(h.row) == 0) {
mapping.emplace(h.row, std::set<int>());
}
mapping.at(h.row).insert(h.track);
}

return mapping;
}

// Lua
#include "Etterna/Models/Lua/LuaBinding.h"

Expand Down
10 changes: 10 additions & 0 deletions src/Etterna/Models/HighScore/Replay.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "Etterna/Models/Misc/EnumHelper.h"
#include "ReplayConstantsAndTypes.h"
#include <set>

struct HighScore;

Expand Down Expand Up @@ -172,6 +173,15 @@ class Replay
auto GenerateInputData() -> bool;
auto GeneratePlaybackEvents() -> std::map<int, std::vector<PlaybackEvent>>;

// 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<int, std::set<int>>;
/// Returns a map of rows to a set of columns which are dropped
/// See which rows have drops using this
auto GenerateDroppedHoldRowsToColumnsMap() -> std::map<int, std::set<int>>;

// Offsets can be really weird - Remove all impossible offsets
inline void ValidateOffsets();

Expand Down

0 comments on commit 4be5c99

Please sign in to comment.