diff --git a/src/Steps.cpp b/src/Steps.cpp index 60039a297b..69b7038c82 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -750,6 +750,7 @@ void Steps::SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] ) m_bAreCachedRadarValuesJustLoaded = true; } + void Steps::SetCachedTechCounts( const TechCounts ts[NUM_PLAYERS] ) { DeAutogen(); @@ -776,6 +777,134 @@ void Steps::SetPeakNps(std::vector &peakNps) m_PeakNps.assign(peakNps.begin(), peakNps.end()); } +const RString Steps::GetGrooveStatsHash() const +{ + return GrooveStatsHash; +} + +void Steps::CalculateGrooveStatsHash() +{ + if (m_bIsCachedGrooveStatsHashJustLoaded == true) + { + m_bIsCachedGrooveStatsHashJustLoaded = false; + return; + } + this->Decompress(); + + RString smNoteData = this->MinimizedChartString(); + + TimingData * timingData = this->GetTimingData(); + std::vector segments = timingData->GetTimingSegments(SEGMENT_BPM); + std::vector bpmStrings; + for (TimingSegment *segment : segments) + { + BPMSegment *bpmSegment = ToBPM(segment); + float beat = bpmSegment->GetBeat(); + float bpm = bpmSegment->GetBPM(); + std::ostringstream os; + os << std::fixed << std::setprecision(3) << beat; + os << "="; + os << std::fixed << std::setprecision(3) << bpm; + bpmStrings.push_back(os.str()); + } + RString bpmString = join(",", bpmStrings); + + smNoteData.append(bpmString); + RString gsKey = BinaryToHex(CryptManager::GetSHA1ForString(smNoteData)); + gsKey = gsKey.substr(0, 16); + GrooveStatsHash = gsKey; +} + +RString Steps::MinimizedChartString() +{ + // We can potentially minimize the chart to get the most compressed + // form of the actual chart data. + // NOTE(teejusb): This can be more compressed than the data actually + // generated by StepMania. This is okay because the charts would still + // be considered equivalent. + // E.g. 0000 0000 + // 0000 -- minimized to --> + // 0000 + // 0000 + // StepMania will always generate the former since quarter notes are + // the smallest quantization. + + RString smNoteData = ""; + + this->GetSMNoteData(smNoteData); + if( smNoteData == "") + { + return ""; + } + RString minimizedNoteData = ""; + + std::vector measures; + Regex anyNote("[^0]"); + + split(smNoteData, ",", measures, true); + for (unsigned m = 0; m < measures.size(); m++) + { + + Trim(measures[m]); + bool isEmpty = true; + bool allZeroes = true; + bool minimal = false; + std::vector lines; + split(measures[m], "\n", lines, true); + while (!minimal && lines.size() % 2 == 0) + { + // If every other line is all 0s, we can minimize the measure + for (unsigned i = 1; i < lines.size(); i += 2) + { + Trim(lines[i]); + if (anyNote.Compare(lines[i]) == true) + { + allZeroes = false; + break; + } + } + + if (allZeroes) + { + // Iterate through lines, removing every other item. + // Note that we're removing the odd indices, so we + // call `++it;` and then erase the following line + auto it = lines.begin(); + while (it != lines.end()) + { + ++it; + if (it != lines.end()) + { + it = lines.erase(it); + } + } + } + else + { + minimal = true; + } + } + + // Once the measure has been minimized, make sure all of the lines are + // actually trimmed. + // (for some reason, the chart returned by GetSMNoteData() have a lot + // of extra newlines) + for (unsigned l = 0; l < lines.size(); l++) + { + Trim(lines[l]); + } + + // Then, rejoin the lines together to make a measure, + // and add it to minimizedNoteData. + minimizedNoteData += join("\n", lines); + if (m < measures.size() - 1) + { + minimizedNoteData += "\n,\n"; + } + } + return minimizedNoteData; +} + RString Steps::GenerateChartKey() { ChartKey = this->GenerateChartKey(*m_pNoteData, this->GetTimingData()); @@ -1019,6 +1148,19 @@ public: return 1; } */ + + static int GetMinimizedChartString(T * p, lua_State *L) + { + lua_pushstring(L, p->MinimizedChartString()); + return 1; + } + + static int GetGrooveStatsHash(T *p, lua_State *L) + { + lua_pushstring(L, p->GetGrooveStatsHash()); + return 1; + } + static int GetChartName(T *p, lua_State *L) { lua_pushstring(L, p->GetChartName()); @@ -1114,6 +1256,8 @@ public: ADD_METHOD( GetDifficulty ); ADD_METHOD( GetFilename ); ADD_METHOD( GetHash ); + ADD_METHOD( GetMinimizedChartString ); + ADD_METHOD( GetGrooveStatsHash ); ADD_METHOD( GetMeter ); ADD_METHOD( HasSignificantTimingChanges ); ADD_METHOD( HasAttacks ); diff --git a/src/Steps.h b/src/Steps.h index 27461941a2..749ab144ff 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -139,6 +139,13 @@ public: /** @brief Produces a chart that's reduced to it's smallest unique representable form. */ RString MinimizedChartString(); + /** @brief Generates a hash used for GrooveStats integration. */ + void CalculateGrooveStatsHash(); + const RString GetGrooveStatsHash() const; + + /** @brief Produces a chart that's reduced to it's smallest unique representable form. */ + RString MinimizedChartString(); + void ChangeFilenamesForCustomSong(); void SetLoadedFromProfile( ProfileSlot slot ) { m_LoadedFromProfile = slot; } @@ -296,6 +303,9 @@ private: + RString GrooveStatsHash; + bool m_bIsCachedGrooveStatsHashJustLoaded; + /** @brief The name of the person who created the Steps. */ RString m_sCredit; /** @brief The name of the chart. */