diff --git a/src/Etterna/Models/HighScore/Replay.cpp b/src/Etterna/Models/HighScore/Replay.cpp index 42d78ed206..caf2621e19 100644 --- a/src/Etterna/Models/HighScore/Replay.cpp +++ b/src/Etterna/Models/HighScore/Replay.cpp @@ -65,7 +65,9 @@ Replay::Replay(HighScore* hs) , fMusicRate(hs->GetMusicRate()) , fSongOffset(hs->GetSongOffset()) { - + rngSeed = 0; + // dont set mods here because it is slow. + // load from disk or when highscore is saving } Replay::~Replay() { @@ -140,6 +142,23 @@ Replay::GetTimingData() -> TimingData* return steps->GetTimingData(); } +auto +Replay::SetHighScoreMods() -> void +{ + auto* hs = GetHighScore(); + if (hs != nullptr) { + auto ms = hs->GetModifiers(); + ms.erase( + std::remove_if(ms.begin(), + ms.end(), + [](unsigned char x) { return std::isspace(x); }), + ms.end()); + mods = ms; + } else { + mods = NO_MODS; + } +} + auto Replay::GetReplaySnapshotForNoterow(int row) -> std::shared_ptr { @@ -270,6 +289,10 @@ Replay::WriteInputData() -> bool return false; } + if (mods.empty()) { + SetHighScoreMods(); + } + const auto path = INPUT_DATA_DIR + scoreKey; const auto path_z = path + "z"; @@ -285,22 +308,35 @@ Replay::WriteInputData() -> bool // it's bad to get this now, but it isn't saved anywhere else float fGlobalOffset = PREFSMAN->m_fGlobalOffsetSeconds.Get(); // header: - // chartkey scorekey rate offset globaloffset - auto headerLine1 = chartKey + " " + scoreKey + " " + - std::to_string(fMusicRate) + " " + - std::to_string(fSongOffset) + " " + - std::to_string(fGlobalOffset) + "\n"; + // chartkey scorekey rate offset globaloffset modstring rngseed + auto modStr = mods.empty() ? NO_MODS : mods; + auto headerLine1 = + chartKey + " " + scoreKey + " " + std::to_string(fMusicRate) + " " + + std::to_string(fSongOffset) + " " + std::to_string(fGlobalOffset) + + " " + modStr + " " + std::to_string(rngSeed) + "\n"; fileStream.write(headerLine1.c_str(), headerLine1.size()); // input data: // column press/lift time nearest_tap tap_offset const unsigned int sz = InputData.size() - 1; - for (unsigned int i = 0; i <= sz; i++) { - append = std::to_string(InputData.at(i).column) + " " + - (InputData.at(i).is_press ? "1" : "0") + " " + - std::to_string(InputData.at(i).songPositionSeconds) + " " + - std::to_string(InputData.at(i).nearestTapNoterow) + " " + - std::to_string(InputData.at(i).offsetFromNearest) + "\n"; + for (auto& data : InputData) { + auto typestr = + data.nearestTapNoteType != TapNoteType_Tap + ? " " + std::to_string(data.nearestTapNoteType) + " " + : ""; + // it would be unusual if typestr was blank and this wasnt. + // it can only happen for TapNoteType_HoldHead + auto subtypestr = + data.nearestTapNoteSubType != TapNoteSubType_Invalid + ? std::to_string(data.nearestTapNoteSubType) + : ""; + + append = std::to_string(data.column) + " " + + (data.is_press ? "1" : "0") + " " + + std::to_string(data.songPositionSeconds) + " " + + std::to_string(data.nearestTapNoterow) + " " + + std::to_string(data.offsetFromNearest) + typestr + + subtypestr + "\n"; fileStream.write(append.c_str(), append.size()); } @@ -475,7 +511,7 @@ Replay::LoadInputData(const std::string& replayDir) -> bool while (ss >> buffer) { tokens.emplace_back(buffer); } - if (tokens.size() != 5) { + if (tokens.size() != 5 && tokens.size() != 7) { Locator::getLogger()->warn("Bad input data header detected: {}", path_z.c_str()); return false; @@ -486,7 +522,12 @@ Replay::LoadInputData(const std::string& replayDir) -> bool this->fMusicRate = std::stof(tokens[2]); this->fSongOffset = std::stof(tokens[3]); this->fGlobalOffset = std::stof(tokens[4]); - + + if (tokens.size() == 7) { + this->mods = tokens[5]; + this->rngSeed = std::stol(tokens[6]); + } + tokens.clear(); } @@ -541,6 +582,17 @@ Replay::LoadInputData(const std::string& replayDir) -> bool ev.songPositionSeconds = std::stof(tokens[2]); ev.nearestTapNoterow = std::stoi(tokens[3]); ev.offsetFromNearest = std::stof(tokens[4]); + + // type data is present + if (tokens.size() >= 6) { + ev.nearestTapNoteType = + static_cast(std::stoi(tokens[5])); + } + if (tokens.size() >= 7) { + ev.nearestTapNoteSubType = + static_cast(std::stoi(tokens[6])); + } + readInputs.push_back(ev); tokens.clear(); @@ -822,23 +874,29 @@ Replay::FillInBlanksForInputData() -> bool return false; } + if (mods.empty()) { + SetHighScoreMods(); + } + for (auto& d : InputData) { auto& row = d.nearestTapNoterow; auto& col = d.column; const auto& tn = notedata.GetTapNote(col, row); if (tn == TAP_EMPTY) { - Locator::getLogger()->warn( + // usually row -1 produces this + // row -1 is the result of tap really far away + // or a tap nearest to a note that is already judged + // (releases near to already judged notes also) + Locator::getLogger()->debug( "WHAT??? row {} col {} time {}", row, col, d.songPositionSeconds); } d.nearestTapNoteType = tn.type; d.nearestTapNoteSubType = tn.subType; } - // should save input data here - // !!!!!! - // TODO: - // TODO: + // save changes + WriteInputData(); return true; } @@ -934,7 +992,7 @@ Replay::GeneratePrimitiveVectors() -> bool // we have replay data but not column data return GenerateReplayV2DataPresumptively(); } - + if (!LoadInputData()) { Locator::getLogger()->warn("Failed to generate primitive vectors for " "score {} because input data is not present", diff --git a/src/Etterna/Models/HighScore/Replay.h b/src/Etterna/Models/HighScore/Replay.h index f77f80ac21..a9a89f8645 100644 --- a/src/Etterna/Models/HighScore/Replay.h +++ b/src/Etterna/Models/HighScore/Replay.h @@ -17,46 +17,57 @@ class Replay Replay(HighScore* hs); ~Replay(); - inline auto GetBasicPath() const -> const std::string { + inline auto GetBasicPath() const -> const std::string + { return BASIC_REPLAY_DIR + scoreKey; } - inline auto GetFullPath() const -> const std::string { + inline auto GetFullPath() const -> const std::string + { return FULL_REPLAY_DIR + scoreKey; } - inline auto GetInputPath() const -> const std::string { + inline auto GetInputPath() const -> const std::string + { return INPUT_DATA_DIR + scoreKey; } - auto GetOffsetVector() const -> const std::vector& { + auto GetOffsetVector() const -> const std::vector& + { return vOffsetVector; } - auto GetCopyOfOffsetVector() const -> std::vector { + auto GetCopyOfOffsetVector() const -> std::vector + { return vOffsetVector; } void SetOffsetVector(const std::vector& v) { vOffsetVector = v; } - auto GetNoteRowVector() const -> const std::vector& { + auto GetNoteRowVector() const -> const std::vector& + { return vNoteRowVector; } - auto GetCopyOfNoteRowVector() const -> std::vector { + auto GetCopyOfNoteRowVector() const -> std::vector + { return vNoteRowVector; } void SetNoteRowVector(const std::vector& v) { vNoteRowVector = v; } - auto GetTrackVector() const -> const std::vector& { + auto GetTrackVector() const -> const std::vector& + { return vTrackVector; } - auto GetCopyOfTrackVector() const -> std::vector { + auto GetCopyOfTrackVector() const -> std::vector + { return vTrackVector; } void SetTrackVector(const std::vector& v) { vTrackVector = v; } - auto GetTapNoteTypeVector() const -> const std::vector& { + auto GetTapNoteTypeVector() const -> const std::vector& + { return vTapNoteTypeVector; } - auto GetCopyOfTapNoteTypeVector() const -> std::vector { + auto GetCopyOfTapNoteTypeVector() const -> std::vector + { return vTapNoteTypeVector; } void SetTapNoteTypeVector(const std::vector& v) @@ -90,7 +101,8 @@ class Replay vMineReplayDataVector = v; } - auto GetOnlineReplayTimestampVector() const -> const std::vector& { + auto GetOnlineReplayTimestampVector() const -> const std::vector& + { return vOnlineReplayTimestampVector; } auto GetCopyOfOnlineReplayTimestampVector() const -> std::vector @@ -102,10 +114,12 @@ class Replay vOnlineReplayTimestampVector = v; } - auto GetInputDataVector() const -> const std::vector& { + auto GetInputDataVector() const -> const std::vector& + { return InputData; } - auto GetCopyOfInputDataVector() const -> std::vector { + auto GetCopyOfInputDataVector() const -> std::vector + { return InputData; } void SetInputDataVector(const std::vector& v) @@ -113,10 +127,12 @@ class Replay InputData = v; } - auto GetReplaySnapshotMap() const -> const std::map& { + auto GetReplaySnapshotMap() const -> const std::map& + { return m_ReplaySnapshotMap; } - auto GetCopyOfReplaySnapshotMap() const -> std::map { + auto GetCopyOfReplaySnapshotMap() const -> std::map + { return m_ReplaySnapshotMap; } void SetReplaySnapshotMap(const std::map& m) @@ -124,46 +140,20 @@ class Replay m_ReplaySnapshotMap = m; } - auto GetJudgeInfo() -> JudgeInfo& { - return judgeInfo; - } - auto GetCopyOfJudgeInfo() const -> JudgeInfo { - return judgeInfo; - } - void SetJudgeInfo(const JudgeInfo& ji) { - judgeInfo = ji; - } + auto GetJudgeInfo() -> JudgeInfo& { return judgeInfo; } + auto GetCopyOfJudgeInfo() const -> JudgeInfo { return judgeInfo; } + void SetJudgeInfo(const JudgeInfo& ji) { judgeInfo = ji; } - auto GetScoreKey() const -> std::string { - return scoreKey; - } - void SetScoreKey(std::string& key) { - scoreKey = key; - } - auto GetChartKey() const -> std::string { - return chartKey; - } - void SetChartKey(std::string& key) { - chartKey = key; - } - auto GetMusicRate() const -> float { - return fMusicRate; - } - void SetMusicRate(float f) { - fMusicRate = f; - } - auto GetSongOffset() const -> float { - return fSongOffset; - } - void SetSongOffset(float f) { - fSongOffset = f; - } - auto GetGlobalOffset() const -> float { - return fGlobalOffset; - } - void SetGlobalOffset(float f) { - fGlobalOffset = f; - } + auto GetScoreKey() const -> std::string { return scoreKey; } + void SetScoreKey(std::string& key) { scoreKey = key; } + auto GetChartKey() const -> std::string { return chartKey; } + void SetChartKey(std::string& key) { chartKey = key; } + auto GetMusicRate() const -> float { return fMusicRate; } + void SetMusicRate(float f) { fMusicRate = f; } + auto GetSongOffset() const -> float { return fSongOffset; } + void SetSongOffset(float f) { fSongOffset = f; } + auto GetGlobalOffset() const -> float { return fGlobalOffset; } + void SetGlobalOffset(float f) { fGlobalOffset = f; } ReplayType GetReplayType() const { @@ -182,8 +172,9 @@ class Replay } } - // true for V2 and InputData - auto HasColumnData() const -> bool { + /// true for V2 and InputData + auto HasColumnData() const -> bool + { const auto t = GetReplayType(); return t >= ReplayType_V2 && t < NUM_ReplayType; } @@ -216,7 +207,7 @@ class Replay /// 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 void ValidateOffsets(); @@ -229,7 +220,8 @@ class Replay auto GetReplaySnapshotForNoterow(int row) -> std::shared_ptr; - void Unload() { + void Unload() + { // stats m_ReplaySnapshotMap.clear(); @@ -253,7 +245,7 @@ class Replay vOnlineReplayTimestampVector.shrink_to_fit(); } - // Lua + /// Lua void PushSelf(lua_State* L); private: @@ -263,11 +255,15 @@ class Replay -> bool; auto LoadInputData(const std::string& replayDir = INPUT_DATA_DIR) -> bool; - /// For V1 or earlier replays lacking column data, we need to assume information. - /// Make it all up. This fills in the column data using NoteData. - /// This also provides TapNoteTypes + /// For V1 or earlier replays lacking column data, we need to assume + /// information. Make it all up. This fills in the column data using + /// NoteData. This also provides TapNoteTypes auto GenerateReplayV2DataPresumptively() -> bool; + /// Setting the mod string is handled separately. + /// Use this to set mods, as long as a scorekey is given. + auto SetHighScoreMods() -> void; + std::map m_ReplaySnapshotMap{}; JudgeInfo judgeInfo{}; @@ -280,6 +276,8 @@ class Replay float fMusicRate = 1.F; float fSongOffset = 0.F; float fGlobalOffset = 0.F; + std::string mods{}; + unsigned int rngSeed = 0u; std::vector InputData; std::vector vOffsetVector; diff --git a/src/Etterna/Models/HighScore/ReplayConstantsAndTypes.h b/src/Etterna/Models/HighScore/ReplayConstantsAndTypes.h index c767951ad1..cbd16cc159 100644 --- a/src/Etterna/Models/HighScore/ReplayConstantsAndTypes.h +++ b/src/Etterna/Models/HighScore/ReplayConstantsAndTypes.h @@ -13,6 +13,8 @@ const std::string FULL_REPLAY_DIR = "Save/ReplaysV2/"; // contains input data files corresponding to replays const std::string INPUT_DATA_DIR = "Save/InputData/"; +const std::string NO_MODS = "none"; + /// enum values defined by Replay.GetReplayType() enum ReplayType { diff --git a/src/Etterna/Singletons/ReplayManager.cpp b/src/Etterna/Singletons/ReplayManager.cpp index c8054a2db4..35da913a66 100644 --- a/src/Etterna/Singletons/ReplayManager.cpp +++ b/src/Etterna/Singletons/ReplayManager.cpp @@ -39,7 +39,7 @@ ReplayManager::ReleaseReplay(Replay* replay) { const auto key = replay->GetScoreKey(); auto it = scoresToReplays.find(key); if (it == scoresToReplays.end()) { - Locator::getLogger()->fatal( + Locator::getLogger()->warn( "Tried to free replay {} with no existing refs! Programming error!", key); } else { @@ -118,7 +118,7 @@ ReplayManager::InitReplayPlaybackForScore(HighScore* hs) void ReplayManager::UnsetActiveReplay() { - if (activeReplay != nullptr) { + if (activeReplay != nullptr && activeReplay != dummyReplay) { ReleaseReplay(activeReplay); } activeReplayScore = nullptr; diff --git a/src/Etterna/Singletons/ReplayManager.h b/src/Etterna/Singletons/ReplayManager.h index a8325343bc..07e70658d3 100644 --- a/src/Etterna/Singletons/ReplayManager.h +++ b/src/Etterna/Singletons/ReplayManager.h @@ -84,10 +84,11 @@ class ReplayManager auto GenerateComboListForReplay(Replay& replay, float timingScale = 1.F) -> std::vector; + /// Lua void PushSelf(lua_State* L); private: - // scorekey to {refcount, pointer} + /// scorekey to {refcount, pointer} std::unordered_map> scoresToReplays{}; };