1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 09:22:54 +08:00

Clean up off-by-one offsets from repeat-related properties

This commit is contained in:
smoogipoo 2018-01-23 13:37:25 +09:00
parent 4f360eac56
commit d37844c068
15 changed files with 101 additions and 110 deletions

View File

@ -21,9 +21,7 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </summary> /// </summary>
private const float base_scoring_distance = 100; private const float base_scoring_distance = 100;
public readonly SliderCurve Curve = new SliderCurve(); public int RepeatCount { get; set; }
public int RepeatCount { get; set; } = 1;
public double Velocity; public double Velocity;
public double TickDistance; public double TickDistance;
@ -55,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Objects
var length = Curve.Distance; var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length); var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity; var spanDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01; var minDistanceFromEnd = Velocity * 0.01;
@ -67,10 +65,10 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X X = X
}); });
for (var repeat = 0; repeat < RepeatCount; repeat++) for (var span = 0; span < this.SpanCount(); span++)
{ {
var repeatStartTime = StartTime + repeat * repeatDuration; var spanStartTime = StartTime + span * spanDuration;
var reversed = repeat % 2 == 1; var reversed = span % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance) for (var d = tickDistance; d <= length; d += tickDistance)
{ {
@ -80,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects
var timeProgress = d / length; var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress; var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
var lastTickTime = repeatStartTime + timeProgress * repeatDuration; var lastTickTime = spanStartTime + timeProgress * spanDuration;
AddNested(new Droplet AddNested(new Droplet
{ {
StartTime = lastTickTime, StartTime = lastTickTime,
@ -95,17 +93,17 @@ namespace osu.Game.Rulesets.Catch.Objects
}); });
} }
double tinyTickInterval = tickDistance / length * repeatDuration; double tinyTickInterval = tickDistance / length * spanDuration;
while (tinyTickInterval > 100) while (tinyTickInterval > 100)
tinyTickInterval /= 2; tinyTickInterval /= 2;
for (double t = 0; t < repeatDuration; t += tinyTickInterval) for (double t = 0; t < spanDuration; t += tinyTickInterval)
{ {
double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration; double progress = reversed ? 1 - t / spanDuration : t / spanDuration;
AddNested(new TinyDroplet AddNested(new TinyDroplet
{ {
StartTime = repeatStartTime + t, StartTime = spanStartTime + t,
ComboColour = ComboColour, ComboColour = ComboColour,
X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
@ -121,15 +119,15 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
Samples = Samples, Samples = Samples,
ComboColour = ComboColour, ComboColour = ComboColour,
StartTime = repeatStartTime + repeatDuration, StartTime = spanStartTime + spanDuration,
X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
}); });
} }
} }
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; public float EndX => Curve.PositionAt(this.ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
@ -139,6 +137,8 @@ namespace osu.Game.Rulesets.Catch.Objects
set { Curve.Distance = value; } set { Curve.Distance = value; }
} }
public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints public List<Vector2> ControlPoints
{ {
get { return Curve.ControlPoints; } get { return Curve.ControlPoints; }
@ -152,17 +152,5 @@ namespace osu.Game.Rulesets.Catch.Objects
get { return Curve.CurveType; } get { return Curve.CurveType; }
set { Curve.CurveType = value; } set { Curve.CurveType = value; }
} }
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
double p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
} }
} }

View File

@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
if (curveData == null) if (curveData == null)
return HitObject.Samples; return HitObject.Samples;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.RepeatCount; double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index]; return curveData.RepeatSamples[index];

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly double endTime; private readonly double endTime;
private readonly double segmentDuration; private readonly double segmentDuration;
private readonly int repeatCount; private readonly int spanCount;
private PatternType convertType; private PatternType convertType;
@ -39,25 +39,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var distanceData = hitObject as IHasDistance; var distanceData = hitObject as IHasDistance;
var repeatsData = hitObject as IHasRepeats; var repeatsData = hitObject as IHasRepeats;
repeatCount = repeatsData?.RepeatCount ?? 1; spanCount = repeatsData?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats // The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * repeatCount; double distance = (distanceData?.Distance ?? 0) * spanCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider // The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// The duration of the osu! hit object // The duration of the osu! hit object
double osuDuration = distance / osuVelocity; double osuDuration = distance / osuVelocity;
endTime = hitObject.StartTime + osuDuration; endTime = hitObject.StartTime + osuDuration;
segmentDuration = (endTime - HitObject.StartTime) / repeatCount; segmentDuration = (endTime - HitObject.StartTime) / spanCount;
} }
public override Pattern Generate() public override Pattern Generate()
{ {
if (repeatCount > 1) if (spanCount > 1)
{ {
if (segmentDuration <= 90) if (segmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1); return generateRandomHoldNotes(HitObject.StartTime, 1);
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (segmentDuration <= 120) if (segmentDuration <= 120)
{ {
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, repeatCount + 1); return generateRandomNotes(HitObject.StartTime, spanCount + 1);
} }
if (segmentDuration <= 160) if (segmentDuration <= 160)
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (duration >= 4000) if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
if (segmentDuration > 400 && repeatCount < TotalColumns - 1 - RandomStart) if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime); return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime); return generateHoldAndNormalNotes(HitObject.StartTime);
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
bool increasing = Random.NextDouble() > 0.5; bool increasing = Random.NextDouble() > 0.5;
for (int i = 0; i <= repeatCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
addToPattern(pattern, column, startTime, startTime); addToPattern(pattern, column, startTime, startTime);
startTime += segmentDuration; startTime += segmentDuration;
@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0));
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i <= repeatCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int columnRepeat = Math.Min(repeatCount, TotalColumns); int columnRepeat = Math.Min(spanCount, TotalColumns);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns)
@ -409,7 +409,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = Random.Next(RandomStart, TotalColumns); int nextColumn = Random.Next(RandomStart, TotalColumns);
var rowPattern = new Pattern(); var rowPattern = new Pattern();
for (int i = 0; i <= repeatCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
if (!(ignoreHead && startTime == HitObject.StartTime)) if (!(ignoreHead && startTime == HitObject.StartTime))
{ {
@ -442,7 +442,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (curveData == null) if (curveData == null)
return HitObject.Samples; return HitObject.Samples;
double segmentTime = (endTime - HitObject.StartTime) / repeatCount; double segmentTime = (endTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index]; return curveData.RepeatSamples[index];

View File

@ -10,6 +10,7 @@ using System.Linq;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -109,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
private int currentRepeat; private int currentSpan;
public bool Tracking; public bool Tracking;
protected override void Update() protected override void Update()
@ -120,17 +121,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
int repeat = slider.RepeatAt(progress); int span = slider.SpanAt(progress);
progress = slider.ProgressAt(progress); progress = slider.ProgressAt(progress);
if (repeat > currentRepeat) if (span > currentSpan)
currentRepeat = repeat; currentSpan = span;
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!InitialCircle.Judgements.Any(j => j.IsHit)) if (!InitialCircle.Judgements.Any(j => j.IsHit))
InitialCircle.Position = slider.Curve.PositionAt(progress); InitialCircle.Position = slider.Curve.PositionAt(progress);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(progress, repeat); foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(progress, span);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in ticks.Children) t.Tracking = Ball.Tracking; foreach (var t in ticks.Children) t.Tracking = Ball.Tracking;
} }

View File

@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
} }
public void UpdateProgress(double progress, int repeat) public void UpdateProgress(double progress, int span)
{ {
Position = slider.Curve.PositionAt(progress); Position = slider.Curve.PositionAt(progress);
} }

View File

@ -15,6 +15,7 @@ using OpenTK;
using OpenTK.Graphics.ES30; using OpenTK.Graphics.ES30;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
@ -164,14 +165,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return true; return true;
} }
public void UpdateProgress(double progress, int repeat) public void UpdateProgress(double progress, int span)
{ {
double start = 0; double start = 0;
double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1;
if (repeat >= slider.RepeatCount - 1) if (span >= slider.SpanCount() - 1)
{ {
if (Math.Min(repeat, slider.RepeatCount - 1) % 2 == 1) if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
{ {
start = 0; start = 0;
end = snakingOut ? progress : 1; end = snakingOut ? progress : 1;

View File

@ -5,6 +5,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public interface ISliderProgress public interface ISliderProgress
{ {
void UpdateProgress(double progress, int repeat); void UpdateProgress(double progress, int span);
} }
} }

View File

@ -20,12 +20,12 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
private const float base_scoring_distance = 100; private const float base_scoring_distance = 100;
public readonly SliderCurve Curve = new SliderCurve(); public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
public override Vector2 EndPosition => PositionAt(1); public override Vector2 EndPosition => this.PositionAt(1);
public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints public List<Vector2> ControlPoints
{ {
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects
internal float LazyTravelDistance; internal float LazyTravelDistance;
public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>(); public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public int RepeatCount { get; set; } = 1; public int RepeatCount { get; set; }
private int stackHeight; private int stackHeight;
@ -88,18 +88,6 @@ namespace osu.Game.Rulesets.Osu.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate; TickDistance = scoringDistance / difficulty.SliderTickRate;
} }
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
double p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
@ -114,14 +102,14 @@ namespace osu.Game.Rulesets.Osu.Objects
var length = Curve.Distance; var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length); var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity; var spanDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01; var minDistanceFromEnd = Velocity * 0.01;
for (var repeat = 0; repeat < RepeatCount; repeat++) for (var span = 0; span < this.SpanCount(); span++)
{ {
var repeatStartTime = StartTime + repeat * repeatDuration; var spanStartTime = StartTime + span * spanDuration;
var reversed = repeat % 2 == 1; var reversed = span % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance) for (var d = tickDistance; d <= length; d += tickDistance)
{ {
@ -144,8 +132,8 @@ namespace osu.Game.Rulesets.Osu.Objects
AddNested(new SliderTick AddNested(new SliderTick
{ {
RepeatIndex = repeat, RepeatIndex = span,
StartTime = repeatStartTime + timeProgress * repeatDuration, StartTime = spanStartTime + timeProgress * spanDuration,
Position = Curve.PositionAt(distanceProgress), Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
@ -160,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
var repeatDuration = Distance / Velocity; var repeatDuration = Distance / Velocity;
for (var repeat = 1; repeat < RepeatCount; repeat++) for (var repeat = 1; repeat <= RepeatCount; repeat++)
{ {
var repeatStartTime = StartTime + repeat * repeatDuration; var repeatStartTime = StartTime + repeat * repeatDuration;

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;

View File

@ -84,14 +84,9 @@ namespace osu.Game.Rulesets.Osu.Tests
private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2) private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2)
{ {
repeats++; // The first run through the slider is considered a repeat
var repeatSamples = new List<List<SampleInfo>>(); var repeatSamples = new List<List<SampleInfo>>();
if (repeats > 1) for (int i = 0; i < repeats; i++)
{ repeatSamples.Add(new List<SampleInfo>());
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
}
var slider = new Slider var slider = new Slider
{ {

View File

@ -84,7 +84,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (distanceData != null) if (distanceData != null)
{ {
int repeats = repeatsData?.RepeatCount ?? 1; // Number of spans of the object - one for the initial length and for each repeat
int spans = repeatsData?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
// The true distance, accounting for any repeats. This ends up being the drum roll distance later // The true distance, accounting for any repeats. This ends up being the drum roll distance later
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier; double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
// The velocity of the taiko hit object - calculated as the velocity of a drum roll // The velocity of the taiko hit object - calculated as the velocity of a drum roll
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double osuDuration = distance / osuVelocity; double osuDuration = distance / osuVelocity;
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / repeats); double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{ {

View File

@ -77,6 +77,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (repeatCount > 9000) if (repeatCount > 9000)
throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
// osu-stable treated the first span of the slider as a repeat, but no repeats are happening
repeatCount = Math.Max(0, repeatCount - 1);
if (split.Length > 7) if (split.Length > 7)
length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
@ -84,8 +88,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
readCustomSampleBanks(split[10], bankInfo); readCustomSampleBanks(split[10], bankInfo);
// One node for each repeat + the start and end nodes // One node for each repeat + the start and end nodes
// Note that the first length of the slider is considered a repeat, but there are no actual repeats happening int nodes = repeatCount + 2;
int nodes = Math.Max(0, repeatCount - 1) + 2;
// Populate node sample bank infos with the default hit object sample bank // Populate node sample bank infos with the default hit object sample bank
var nodeBankInfos = new List<SampleBankInfo>(); var nodeBankInfos = new List<SampleBankInfo>();
@ -128,7 +131,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
// Generate the final per-node samples // Generate the final per-node samples
var nodeSamples = new List<List<SampleInfo>>(nodes); var nodeSamples = new List<List<SampleInfo>>(nodes);
for (int i = 0; i <= repeatCount; i++) for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples); result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples);

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using OpenTK; using OpenTK;
using osu.Game.Audio; using osu.Game.Audio;
@ -18,34 +17,23 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// </summary> /// </summary>
private const float base_scoring_distance = 100; private const float base_scoring_distance = 100;
/// <summary>
/// <see cref="ConvertSlider"/>s don't need a curve since they're converted to ruleset-specific hitobjects.
/// </summary>
public SliderCurve Curve { get; } = null;
public List<Vector2> ControlPoints { get; set; } public List<Vector2> ControlPoints { get; set; }
public CurveType CurveType { get; set; } public CurveType CurveType { get; set; }
public double Distance { get; set; } public double Distance { get; set; }
public List<List<SampleInfo>> RepeatSamples { get; set; } public List<List<SampleInfo>> RepeatSamples { get; set; }
public int RepeatCount { get; set; } = 1; public int RepeatCount { get; set; }
public double EndTime => StartTime + RepeatCount * Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
public double Velocity = 1; public double Velocity = 1;
public Vector2 PositionAt(double progress)
{
throw new NotImplementedException();
}
public double ProgressAt(double progress)
{
throw new NotImplementedException();
}
public int RepeatAt(double progress)
{
throw new NotImplementedException();
}
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);

View File

@ -11,6 +11,11 @@ namespace osu.Game.Rulesets.Objects.Types
/// </summary> /// </summary>
public interface IHasCurve : IHasDistance, IHasRepeats public interface IHasCurve : IHasDistance, IHasRepeats
{ {
/// <summary>
/// The curve.
/// </summary>
SliderCurve Curve { get; }
/// <summary> /// <summary>
/// The control points that shape the curve. /// The control points that shape the curve.
/// </summary> /// </summary>
@ -20,7 +25,10 @@ namespace osu.Game.Rulesets.Objects.Types
/// The type of curve. /// The type of curve.
/// </summary> /// </summary>
CurveType CurveType { get; } CurveType CurveType { get; }
}
public static class HasCurveExtensions
{
/// <summary> /// <summary>
/// Computes the position on the curve at a given progress, accounting for repeat logic. /// Computes the position on the curve at a given progress, accounting for repeat logic.
/// <para> /// <para>
@ -28,20 +36,28 @@ namespace osu.Game.Rulesets.Objects.Types
/// </para> /// </para>
/// </summary> /// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param> /// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
Vector2 PositionAt(double progress); public static Vector2 PositionAt(this IHasCurve obj, double progress)
=> obj.Curve.PositionAt(obj.ProgressAt(progress));
/// <summary> /// <summary>
/// Finds the progress along the curve, accounting for repeat logic. /// Finds the progress along the curve, accounting for repeat logic.
/// </summary> /// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param> /// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns> /// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
double ProgressAt(double progress); public static double ProgressAt(this IHasCurve obj, double progress)
{
double p = progress * obj.SpanCount() % 1;
if (obj.SpanAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
/// <summary> /// <summary>
/// Determines which repeat of the curve the progress point is on. /// Determines which span of the curve the progress point is on.
/// </summary> /// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param> /// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
/// <returns>[0, RepeatCount] where 0 is the first run.</returns> /// <returns>[0, SpanCount) where 0 is the first run.</returns>
int RepeatAt(double progress); public static int SpanAt(this IHasCurve obj, double progress)
=> (int)(progress * obj.SpanCount());
} }
} }

View File

@ -21,4 +21,13 @@ namespace osu.Game.Rulesets.Objects.Types
/// </summary> /// </summary>
List<List<SampleInfo>> RepeatSamples { get; } List<List<SampleInfo>> RepeatSamples { get; }
} }
public static class HasRepeatsExtensions
{
/// <summary>
/// The amount of times the length of this <see cref="IHasRepeats"/> spans.
/// </summary>
/// <param name="obj">The object that has repeats.</param>
public static int SpanCount(this IHasRepeats obj) => obj.RepeatCount + 1;
}
} }