diff --git a/Beatmap/include/Beatmap/LineGraph.hpp b/Beatmap/include/Beatmap/LineGraph.hpp index be2413daa..786975df7 100644 --- a/Beatmap/include/Beatmap/LineGraph.hpp +++ b/Beatmap/include/Beatmap/LineGraph.hpp @@ -34,6 +34,9 @@ class LineGraph { void Insert(MapTime time, const Point& point); void Insert(MapTime time, const std::string& point); + void RangeSet(MapTime begin, MapTime end, double value); + void RangeAdd(MapTime begin, MapTime end, double delta); + /// Returns the value being extended. double Extend(MapTime time); diff --git a/Beatmap/src/BeatmapFromKSH.cpp b/Beatmap/src/BeatmapFromKSH.cpp index 4df0a1e54..6ed9ab1dd 100755 --- a/Beatmap/src/BeatmapFromKSH.cpp +++ b/Beatmap/src/BeatmapFromKSH.cpp @@ -614,6 +614,10 @@ bool Beatmap::m_ProcessKShootMap(BinaryStream &input, bool metadataOnly) float laserRanges[2] = {1.0f, 1.0f}; MapTime lastLaserPointTime[2] = {0, 0}; + // Stops will be applied after the scroll speed graph is constructed. + // Tuple of (stopBegin, stopEnd, isOverlappingStop) + Vector> stops; + MapTime lastMapTime = 0; uint32 currentTick = 0; @@ -925,14 +929,17 @@ bool Beatmap::m_ProcessKShootMap(BinaryStream &input, bool metadataOnly) } else if (p.first == "stop") { - MapTime stopDuration = Math::RoundToInt((atol(*p.second) / 192.0f) * (currTimingPoint->beatDuration) * 4); + // Stops will be applied after the scroll speed graph is constructed. + const MapTime stopDuration = Math::RoundToInt((atol(*p.second) / 192.0f) * (currTimingPoint->beatDuration) * 4); + bool isOverlappingStop = false; - LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); + if (!stops.empty() && mapTime < std::get<1>(*stops.rbegin())) + { + isOverlappingStop = true; + std::get<2>(*stops.rbegin()) = true; + } - const double endValue = scrollSpeedGraph.ValueAt(mapTime + stopDuration); - scrollSpeedGraph.Extend(mapTime); - scrollSpeedGraph.Insert(mapTime, 0.0); - scrollSpeedGraph.Insert(mapTime + stopDuration, LineGraph::Point{0.0, endValue}); + stops.Add(std::make_tuple(mapTime, mapTime + stopDuration, isOverlappingStop)); } else if (p.first == "scroll_speed") { @@ -1340,7 +1347,30 @@ bool Beatmap::m_ProcessKShootMap(BinaryStream &input, bool metadataOnly) currentTick += static_cast((tickResolution * 4 * currTimingPoint->numerator / currTimingPoint->denominator) / block.ticks.size()); } - //Add chart end event + // Apply stops + for (const auto& stop : stops) + { + const MapTime stopBegin = std::get<0>(stop); + const MapTime stopEnd = std::get<1>(stop); + const bool isOverlapping = std::get<2>(stop); + + LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); + + // In older versions of USC there was a bug where overlapping stop regions made notes scrolling backwards. + // This bug was utilized as gimmicks for several charts, so for backwards compatibility this bug is reimplemented. + // (i.e. the chart would simply not move) + + if (isOverlapping) + { + scrollSpeedGraph.RangeAdd(stopBegin, stopEnd, -1.0); + } + else + { + scrollSpeedGraph.RangeSet(stopBegin, stopEnd, 0.0); + } + } + + // Add chart end event EventObjectState *evt = new EventObjectState(); evt->time = lastMapTime + 2000; evt->key = EventKey::ChartEnd; diff --git a/Beatmap/src/LineGraph.cpp b/Beatmap/src/LineGraph.cpp index 366736261..be6f4baa6 100644 --- a/Beatmap/src/LineGraph.cpp +++ b/Beatmap/src/LineGraph.cpp @@ -47,6 +47,49 @@ void LineGraph::Insert(MapTime mapTime, const std::string& point) } } +void LineGraph::RangeSet(MapTime begin, MapTime end, double value) +{ + if (begin >= end) return; + + const double beginValue = ValueAt(begin); + const double endValue = ValueAt(end); + + const auto beginIt = m_points.lower_bound(begin); + const auto endIt = m_points.upper_bound(end); + + for (auto it = beginIt; it != endIt; it = m_points.erase(it)); + + Insert(begin, LineGraph::Point{beginValue, value}); + Insert(end, LineGraph::Point{value, endValue}); +} + +void LineGraph::RangeAdd(MapTime begin, MapTime end, double delta) +{ + if (begin >= end) return; + + const double beginValue = ValueAt(begin); + const double endValue = ValueAt(end); + + const auto beginIt = m_points.upper_bound(begin); + const auto endIt = m_points.lower_bound(end); + + for (auto it = beginIt; it != endIt; ++it) + { + it->second.value.first += delta; + it->second.value.second += delta; + } + + Insert(begin, LineGraph::Point{beginValue, beginValue + delta}); + + if (endIt != m_points.end() && endIt->first == end) + { + endIt->second.value.first += delta; + } else + { + Insert(end, LineGraph::Point{endValue + delta, endValue}); + } +} + double LineGraph::Extend(MapTime time) { if (m_points.empty())