mirror of
https://github.com/ppy/osu.git
synced 2025-02-27 12:42:55 +08:00
Merge pull request #20809 from peppy/fix-taiko-broken-drum-roll-ticks
Fix taiko drum roll ticks sometimes overflowing outside the drum roll itself
This commit is contained in:
commit
703a8afb11
@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void addControlPoints(IList<MultiplierControlPoint> controlPoints, double sequenceStartTime)
|
||||
{
|
||||
controlPoints.ForEach(point => point.StartTime += sequenceStartTime);
|
||||
controlPoints.ForEach(point => point.Time += sequenceStartTime);
|
||||
|
||||
scrollContainers.ForEach(container =>
|
||||
{
|
||||
@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
foreach (var playfield in playfields)
|
||||
{
|
||||
foreach (var controlPoint in controlPoints)
|
||||
playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime));
|
||||
playfield.Add(createDrawablePoint(playfield, controlPoint.Time));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,11 +9,8 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>, IDeepCloneable<ControlPoint>, IEquatable<ControlPoint>
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>, IDeepCloneable<ControlPoint>, IEquatable<ControlPoint>, IControlPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double Time { get; set; }
|
||||
|
||||
|
@ -196,8 +196,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <param name="time">The time to find the control point at.</param>
|
||||
/// <param name="fallback">The control point to use when <paramref name="time"/> is before any control points.</param>
|
||||
/// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns>
|
||||
protected T BinarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T fallback)
|
||||
where T : ControlPoint
|
||||
public static T BinarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T fallback)
|
||||
where T : class, IControlPoint
|
||||
{
|
||||
return BinarySearch(list, time) ?? fallback;
|
||||
}
|
||||
@ -207,9 +207,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// </summary>
|
||||
/// <param name="list">The list to search.</param>
|
||||
/// <param name="time">The time to find the control point at.</param>
|
||||
/// <returns>The active control point at <paramref name="time"/>.</returns>
|
||||
protected virtual T BinarySearch<T>(IReadOnlyList<T> list, double time)
|
||||
where T : ControlPoint
|
||||
/// <returns>The active control point at <paramref name="time"/>. Will return <c>null</c> if there are no control points, or if the time is before the first control point.</returns>
|
||||
public static T BinarySearch<T>(IReadOnlyList<T> list, double time)
|
||||
where T : class, IControlPoint
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
|
13
osu.Game/Beatmaps/ControlPoints/IControlPoint.cs
Normal file
13
osu.Game/Beatmaps/ControlPoints/IControlPoint.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public interface IControlPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
/// </summary>
|
||||
double Time { get; }
|
||||
}
|
||||
}
|
@ -11,12 +11,12 @@ namespace osu.Game.Rulesets.Timing
|
||||
/// <summary>
|
||||
/// A control point which adds an aggregated multiplier based on the provided <see cref="TimingPoint"/>'s BeatLength and <see cref="EffectPoint"/>'s SpeedMultiplier.
|
||||
/// </summary>
|
||||
public class MultiplierControlPoint : IComparable<MultiplierControlPoint>
|
||||
public class MultiplierControlPoint : IComparable<MultiplierControlPoint>, IControlPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The time in milliseconds at which this <see cref="MultiplierControlPoint"/> starts.
|
||||
/// </summary>
|
||||
public double StartTime;
|
||||
public double Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The aggregate multiplier which this <see cref="MultiplierControlPoint"/> provides.
|
||||
@ -54,13 +54,13 @@ namespace osu.Game.Rulesets.Timing
|
||||
/// <summary>
|
||||
/// Creates a <see cref="MultiplierControlPoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of this <see cref="MultiplierControlPoint"/>.</param>
|
||||
public MultiplierControlPoint(double startTime)
|
||||
/// <param name="time">The start time of this <see cref="MultiplierControlPoint"/>.</param>
|
||||
public MultiplierControlPoint(double time)
|
||||
{
|
||||
StartTime = startTime;
|
||||
Time = time;
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
public int CompareTo(MultiplierControlPoint other) => StartTime.CompareTo(other?.StartTime);
|
||||
public int CompareTo(MultiplierControlPoint other) => Time.CompareTo(other?.Time);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
return -PositionAt(startTime, endTime, timeRange, scrollLength);
|
||||
}
|
||||
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
|
||||
=> (float)((time - currentTime) / timeRange * scrollLength);
|
||||
|
||||
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
|
||||
|
@ -53,8 +53,9 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
/// <param name="currentTime">The current time.</param>
|
||||
/// <param name="timeRange">The amount of visible time.</param>
|
||||
/// <param name="scrollLength">The absolute spatial length through <paramref name="timeRange"/>.</param>
|
||||
/// <param name="originTime">The time to be used for control point lookups (ie. the parent's start time for nested hit objects).</param>
|
||||
/// <returns>The absolute spatial position.</returns>
|
||||
float PositionAt(double time, double currentTime, double timeRange, float scrollLength);
|
||||
float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the time which brings a point to a provided spatial position given the current time.
|
||||
@ -63,7 +64,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
/// <param name="currentTime">The current time.</param>
|
||||
/// <param name="timeRange">The amount of visible time.</param>
|
||||
/// <param name="scrollLength">The absolute spatial length through <paramref name="timeRange"/>.</param>
|
||||
/// <returns>The time at which <see cref="PositionAt(double,double,double,float)"/> == <paramref name="position"/>.</returns>
|
||||
/// <returns>The time at which <see cref="PositionAt(double,double,double,float, double?)"/> == <paramref name="position"/>.</returns>
|
||||
double TimeAt(float position, double currentTime, double timeRange, float scrollLength);
|
||||
|
||||
/// <summary>
|
||||
|
@ -4,22 +4,20 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Timing;
|
||||
|
||||
namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
{
|
||||
public class OverlappingScrollAlgorithm : IScrollAlgorithm
|
||||
{
|
||||
private readonly MultiplierControlPoint searchPoint;
|
||||
|
||||
private readonly SortedList<MultiplierControlPoint> controlPoints;
|
||||
|
||||
public OverlappingScrollAlgorithm(SortedList<MultiplierControlPoint> controlPoints)
|
||||
{
|
||||
this.controlPoints = controlPoints;
|
||||
|
||||
searchPoint = new MultiplierControlPoint();
|
||||
}
|
||||
|
||||
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
|
||||
@ -37,8 +35,8 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
return -PositionAt(startTime, endTime, timeRange, scrollLength);
|
||||
}
|
||||
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
|
||||
=> (float)((time - currentTime) / timeRange * controlPointAt(time).Multiplier * scrollLength);
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
|
||||
=> (float)((time - currentTime) / timeRange * controlPointAt(originTime ?? time).Multiplier * scrollLength);
|
||||
|
||||
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
|
||||
{
|
||||
@ -52,7 +50,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
for (; i < controlPoints.Count; i++)
|
||||
{
|
||||
float lastPos = pos;
|
||||
pos = PositionAt(controlPoints[i].StartTime, currentTime, timeRange, scrollLength);
|
||||
pos = PositionAt(controlPoints[i].Time, currentTime, timeRange, scrollLength);
|
||||
|
||||
if (pos > position)
|
||||
{
|
||||
@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
|
||||
i = Math.Clamp(i, 0, controlPoints.Count - 1);
|
||||
|
||||
return controlPoints[i].StartTime + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength;
|
||||
return controlPoints[i].Time + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
@ -78,19 +76,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
/// <returns>The <see cref="MultiplierControlPoint"/>.</returns>
|
||||
private MultiplierControlPoint controlPointAt(double time)
|
||||
{
|
||||
if (controlPoints.Count == 0)
|
||||
return new MultiplierControlPoint(double.NegativeInfinity);
|
||||
|
||||
if (time < controlPoints[0].StartTime)
|
||||
return controlPoints[0];
|
||||
|
||||
searchPoint.StartTime = time;
|
||||
int index = controlPoints.BinarySearch(searchPoint);
|
||||
|
||||
if (index < 0)
|
||||
index = ~index - 1;
|
||||
|
||||
return controlPoints[index];
|
||||
return ControlPointInfo.BinarySearch(controlPoints, time)
|
||||
// The standard binary search will fail if there's no control points, or if the time is before the first.
|
||||
// For this method, we want to use the first control point in the latter case.
|
||||
?? controlPoints.FirstOrDefault()
|
||||
?? new MultiplierControlPoint(double.NegativeInfinity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
return (float)(objectLength * scrollLength);
|
||||
}
|
||||
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
|
||||
{
|
||||
double timelineLength = relativePositionAt(time, timeRange) - relativePositionAt(currentTime, timeRange);
|
||||
return (float)(timelineLength * scrollLength);
|
||||
@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
if (controlPoints.Count == 0)
|
||||
return;
|
||||
|
||||
positionMappings.Add(new PositionMapping(controlPoints[0].StartTime, controlPoints[0]));
|
||||
positionMappings.Add(new PositionMapping(controlPoints[0].Time, controlPoints[0]));
|
||||
|
||||
for (int i = 0; i < controlPoints.Count - 1; i++)
|
||||
{
|
||||
@ -129,9 +129,9 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
var next = controlPoints[i + 1];
|
||||
|
||||
// Figure out how much of the time range the duration represents, and adjust it by the speed multiplier
|
||||
float length = (float)((next.StartTime - current.StartTime) / timeRange * current.Multiplier);
|
||||
float length = (float)((next.Time - current.Time) / timeRange * current.Multiplier);
|
||||
|
||||
positionMappings.Add(new PositionMapping(next.StartTime, next, positionMappings[^1].Position + length));
|
||||
positionMappings.Add(new PositionMapping(next.Time, next, positionMappings[^1].Position + length));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,9 +158,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
// Trim unwanted sequences of timing changes
|
||||
timingChanges = timingChanges
|
||||
// Collapse sections after the last hit object
|
||||
.Where(s => s.StartTime <= lastObjectTime)
|
||||
.Where(s => s.Time <= lastObjectTime)
|
||||
// Collapse sections with the same start time
|
||||
.GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime);
|
||||
.GroupBy(s => s.Time).Select(g => g.Last()).OrderBy(s => s.Time);
|
||||
|
||||
ControlPoints.AddRange(timingChanges);
|
||||
|
||||
|
@ -93,9 +93,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
/// <summary>
|
||||
/// Given a time, return the position along the scrolling axis within this <see cref="HitObjectContainer"/> at time <paramref name="currentTime"/>.
|
||||
/// </summary>
|
||||
public float PositionAtTime(double time, double currentTime)
|
||||
public float PositionAtTime(double time, double currentTime, double? originTime = null)
|
||||
{
|
||||
float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength);
|
||||
float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength, originTime);
|
||||
return axisInverted ? -scrollPosition : scrollPosition;
|
||||
}
|
||||
|
||||
@ -236,8 +236,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime);
|
||||
}
|
||||
|
||||
private void updateLayoutRecursive(DrawableHitObject hitObject)
|
||||
private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null)
|
||||
{
|
||||
parentHitObjectStartTime ??= hitObject.HitObject.StartTime;
|
||||
|
||||
if (hitObject.HitObject is IHasDuration e)
|
||||
{
|
||||
float length = LengthAtTime(hitObject.HitObject.StartTime, e.EndTime);
|
||||
@ -249,17 +251,17 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
|
||||
foreach (var obj in hitObject.NestedHitObjects)
|
||||
{
|
||||
updateLayoutRecursive(obj);
|
||||
updateLayoutRecursive(obj, parentHitObjectStartTime);
|
||||
|
||||
// Nested hitobjects don't need to scroll, but they do need accurate positions and start lifetime
|
||||
updatePosition(obj, hitObject.HitObject.StartTime);
|
||||
updatePosition(obj, hitObject.HitObject.StartTime, parentHitObjectStartTime);
|
||||
setComputedLifetimeStart(obj.Entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePosition(DrawableHitObject hitObject, double currentTime)
|
||||
private void updatePosition(DrawableHitObject hitObject, double currentTime, double? parentHitObjectStartTime = null)
|
||||
{
|
||||
float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime);
|
||||
float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime, parentHitObjectStartTime);
|
||||
|
||||
if (scrollingAxis == Direction.Horizontal)
|
||||
hitObject.X = position;
|
||||
|
@ -99,8 +99,8 @@ namespace osu.Game.Tests.Visual
|
||||
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
|
||||
=> implementation.GetLength(startTime, endTime, timeRange, scrollLength);
|
||||
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
|
||||
=> implementation.PositionAt(time, currentTime, timeRange, scrollLength);
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
|
||||
=> implementation.PositionAt(time, currentTime, timeRange, scrollLength, originTime);
|
||||
|
||||
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
|
||||
=> implementation.TimeAt(position, currentTime, timeRange, scrollLength);
|
||||
|
Loading…
Reference in New Issue
Block a user