From e5e454ddcda5dda65221723e75116cd56dcc08e8 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Mar 2019 16:17:12 +0900 Subject: [PATCH 01/59] Don't perform lookup of beatmap stats unless an online id is present --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 604d7a132b..d9334d65ac 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - if (Beatmap == null) + if (Beatmap?.OnlineBeatmapID == null) { clearStats(); return; From 054db480897a6fbea1a41f6ec90c5a7c9f3435c9 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Mar 2019 16:59:43 +0900 Subject: [PATCH 02/59] Move online id null check to only bypass metrics lookup --- osu.Game/Screens/Select/BeatmapDetails.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index d9334d65ac..07db073edb 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - if (Beatmap?.OnlineBeatmapID == null) + if (Beatmap == null) { clearStats(); return; @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Select tags.Text = Beatmap.Metadata.Tags; var requestedBeatmap = Beatmap; - if (requestedBeatmap.Metrics == null) + if (requestedBeatmap.Metrics == null && requestedBeatmap.OnlineBeatmapID != null) { var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); lookup.Success += res => From 551380dd4218b7b194b26762e8f0f75dd81c0d37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2019 13:48:45 +0900 Subject: [PATCH 03/59] Extract slider tick creation so it can be shared with osu!catch --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 163 +++++++----------- .../Beatmaps/Formats/SliderEventGenerator.cs | 115 ++++++++++++ 2 files changed, 175 insertions(+), 103 deletions(-) create mode 100644 osu.Game/Beatmaps/Formats/SliderEventGenerator.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 345f599b9d..b23be96b93 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; @@ -12,6 +11,7 @@ using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; @@ -155,116 +155,73 @@ namespace osu.Game.Rulesets.Osu.Objects { base.CreateNestedHitObjects(); - createSliderEnds(); - createTicks(); - createRepeatPoints(); - - if (LegacyLastTickOffset != null) - TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value); - } - - private void createSliderEnds() - { - HeadCircle = new SliderCircle + foreach (var e in + SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - StartTime = StartTime, - Position = Position, - Samples = getNodeSamples(0), - SampleControlPoint = SampleControlPoint, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; + var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL) + ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + var sampleList = new List(); - TailCircle = new SliderTailCircle(this) - { - StartTime = EndTime, - Position = EndPosition, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; - - AddNested(HeadCircle); - AddNested(TailCircle); - } - - private void createTicks() - { - // A very lenient maximum length of a slider for ticks to be generated. - // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. - const double max_length = 100000; - - var length = Math.Min(max_length, Path.Distance); - var tickDistance = MathHelper.Clamp(TickDistance, 0, length); - - if (tickDistance == 0) return; - - var minDistanceFromEnd = Velocity * 10; - - var spanCount = this.SpanCount(); - - for (var span = 0; span < spanCount; span++) - { - var spanStartTime = StartTime + span * SpanDuration; - var reversed = span % 2 == 1; - - for (var d = tickDistance; d <= length; d += tickDistance) - { - if (d > length - minDistanceFromEnd) - break; - - var distanceProgress = d / length; - var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - - var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL) - ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - var sampleList = new List(); - - if (firstSample != null) - sampleList.Add(new SampleInfo - { - Bank = firstSample.Bank, - Volume = firstSample.Volume, - Name = @"slidertick", - }); - - AddNested(new SliderTick + if (firstSample != null) + sampleList.Add(new SampleInfo { - SpanIndex = span, - SpanStartTime = spanStartTime, - StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Position + Path.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - Samples = sampleList + Bank = firstSample.Bank, + Volume = firstSample.Volume, + Name = @"slidertick", }); + + switch (e.Type) + { + case SliderEventType.Tick: + AddNested(new SliderTick + { + SpanIndex = e.SpanIndex, + SpanStartTime = e.SpanStartTime, + StartTime = e.StartTime, + Position = Position + Path.PositionAt(e.PathProgress), + StackHeight = StackHeight, + Scale = Scale, + Samples = sampleList + }); + break; + case SliderEventType.Head: + AddNested(HeadCircle = new SliderCircle + { + StartTime = e.StartTime, + Position = Position, + Samples = getNodeSamples(0), + SampleControlPoint = SampleControlPoint, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboIndex = ComboIndex, + }); + break; + case SliderEventType.Tail: + AddNested(TailCircle = new SliderTailCircle(this) + { + StartTime = e.StartTime, + Position = EndPosition, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboIndex = ComboIndex, + }); + break; + case SliderEventType.Repeat: + AddNested(new RepeatPoint + { + RepeatIndex = e.SpanIndex, + SpanDuration = SpanDuration, + StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, + Position = Position + Path.PositionAt((e.SpanIndex + 1) % 2), + StackHeight = StackHeight, + Scale = Scale, + Samples = getNodeSamples(1 + e.SpanIndex) + }); + break; } } } - private void createRepeatPoints() - { - for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) - { - AddNested(new RepeatPoint - { - RepeatIndex = repeatIndex, - SpanDuration = SpanDuration, - StartTime = StartTime + repeat * SpanDuration, - Position = Position + Path.PositionAt(repeat % 2), - StackHeight = StackHeight, - Scale = Scale, - Samples = getNodeSamples(1 + repeatIndex) - }); - } - } - - private List getNodeSamples(int nodeIndex) - { - if (nodeIndex < NodeSamples.Count) - return NodeSamples[nodeIndex]; - - return Samples; - } + private List getNodeSamples(int nodeIndex) => + nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; public override Judgement CreateJudgement() => new OsuJudgement(); } diff --git a/osu.Game/Beatmaps/Formats/SliderEventGenerator.cs b/osu.Game/Beatmaps/Formats/SliderEventGenerator.cs new file mode 100644 index 0000000000..afe46f42f3 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/SliderEventGenerator.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osuTK; + +namespace osu.Game.Beatmaps.Formats +{ + public static class SliderEventGenerator + { + public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) + { + List events = new List(); + + // A very lenient maximum length of a slider for ticks to be generated. + // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. + const double max_length = 100000; + + var length = Math.Min(max_length, totalDistance); + tickDistance = MathHelper.Clamp(tickDistance, 0, length); + + { + var minDistanceFromEnd = velocity * 10; + + events.Add(new SliderEventDescriptor + { + Type = SliderEventType.Head, + SpanIndex = 0, + SpanStartTime = startTime, + StartTime = startTime, + PathProgress = 0, + }); + + if (tickDistance != 0) + { + for (var span = 0; span < spanCount; span++) + { + var spanStartTime = startTime + span * spanDuration; + var reversed = span % 2 == 1; + + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d > length - minDistanceFromEnd) + break; + + var pathProgress = d / length; + var timeProgress = reversed ? 1 - pathProgress : pathProgress; + + events.Add(new SliderEventDescriptor + { + Type = SliderEventType.Tick, + SpanIndex = span, + SpanStartTime = spanStartTime, + StartTime = spanStartTime + timeProgress * spanDuration, + PathProgress = pathProgress, + }); + } + + if (span < spanCount - 1) + { + events.Add(new SliderEventDescriptor + { + Type = SliderEventType.Repeat, + SpanIndex = span, + SpanStartTime = startTime + span * spanDuration, + StartTime = spanStartTime + (span + 1) * spanDuration, + PathProgress = 1, + }); + } + } + } + + double totalDuration = spanCount * spanDuration; + + var tail = new SliderEventDescriptor + { + Type = SliderEventType.Tail, + SpanIndex = spanCount - 1, + SpanStartTime = startTime + (spanCount - 1) * spanDuration, + StartTime = startTime + totalDuration, + PathProgress = 1, + }; + + if (legacyLastTickOffset != null) + tail.StartTime = Math.Max(startTime + totalDuration / 2, tail.StartTime - legacyLastTickOffset.Value); + + events.Add(tail); + + return events; + } + } + } + + public class SliderEventDescriptor + { + public SliderEventType Type; + + public int SpanIndex; + + public double SpanStartTime; + + public double StartTime; + + public double PathProgress; + } + + public enum SliderEventType + { + Tick, + Head, + Tail, + Repeat + } +} From 973f29b7653a5e57e17be02b268f9ac07c8076c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2019 15:14:57 +0900 Subject: [PATCH 04/59] Apply review --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 3 +- .../Beatmaps/Formats/SliderEventGenerator.cs | 115 ------------------ .../Rulesets/Objects/SliderEventGenerator.cs | 109 +++++++++++++++++ 3 files changed, 110 insertions(+), 117 deletions(-) delete mode 100644 osu.Game/Beatmaps/Formats/SliderEventGenerator.cs create mode 100644 osu.Game/Rulesets/Objects/SliderEventGenerator.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index b23be96b93..9638d5e6f0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -11,7 +11,6 @@ using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; @@ -210,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = e.SpanIndex, SpanDuration = SpanDuration, StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, - Position = Position + Path.PositionAt((e.SpanIndex + 1) % 2), + Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, Samples = getNodeSamples(1 + e.SpanIndex) diff --git a/osu.Game/Beatmaps/Formats/SliderEventGenerator.cs b/osu.Game/Beatmaps/Formats/SliderEventGenerator.cs deleted file mode 100644 index afe46f42f3..0000000000 --- a/osu.Game/Beatmaps/Formats/SliderEventGenerator.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using osuTK; - -namespace osu.Game.Beatmaps.Formats -{ - public static class SliderEventGenerator - { - public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) - { - List events = new List(); - - // A very lenient maximum length of a slider for ticks to be generated. - // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. - const double max_length = 100000; - - var length = Math.Min(max_length, totalDistance); - tickDistance = MathHelper.Clamp(tickDistance, 0, length); - - { - var minDistanceFromEnd = velocity * 10; - - events.Add(new SliderEventDescriptor - { - Type = SliderEventType.Head, - SpanIndex = 0, - SpanStartTime = startTime, - StartTime = startTime, - PathProgress = 0, - }); - - if (tickDistance != 0) - { - for (var span = 0; span < spanCount; span++) - { - var spanStartTime = startTime + span * spanDuration; - var reversed = span % 2 == 1; - - for (var d = tickDistance; d <= length; d += tickDistance) - { - if (d > length - minDistanceFromEnd) - break; - - var pathProgress = d / length; - var timeProgress = reversed ? 1 - pathProgress : pathProgress; - - events.Add(new SliderEventDescriptor - { - Type = SliderEventType.Tick, - SpanIndex = span, - SpanStartTime = spanStartTime, - StartTime = spanStartTime + timeProgress * spanDuration, - PathProgress = pathProgress, - }); - } - - if (span < spanCount - 1) - { - events.Add(new SliderEventDescriptor - { - Type = SliderEventType.Repeat, - SpanIndex = span, - SpanStartTime = startTime + span * spanDuration, - StartTime = spanStartTime + (span + 1) * spanDuration, - PathProgress = 1, - }); - } - } - } - - double totalDuration = spanCount * spanDuration; - - var tail = new SliderEventDescriptor - { - Type = SliderEventType.Tail, - SpanIndex = spanCount - 1, - SpanStartTime = startTime + (spanCount - 1) * spanDuration, - StartTime = startTime + totalDuration, - PathProgress = 1, - }; - - if (legacyLastTickOffset != null) - tail.StartTime = Math.Max(startTime + totalDuration / 2, tail.StartTime - legacyLastTickOffset.Value); - - events.Add(tail); - - return events; - } - } - } - - public class SliderEventDescriptor - { - public SliderEventType Type; - - public int SpanIndex; - - public double SpanStartTime; - - public double StartTime; - - public double PathProgress; - } - - public enum SliderEventType - { - Tick, - Head, - Tail, - Repeat - } -} diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs new file mode 100644 index 0000000000..39827ef532 --- /dev/null +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osuTK; + +namespace osu.Game.Rulesets.Objects +{ + public static class SliderEventGenerator + { + public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) + { + // A very lenient maximum length of a slider for ticks to be generated. + // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. + const double max_length = 100000; + + var length = Math.Min(max_length, totalDistance); + tickDistance = MathHelper.Clamp(tickDistance, 0, length); + + var minDistanceFromEnd = velocity * 10; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Head, + SpanIndex = 0, + SpanStartTime = startTime, + StartTime = startTime, + PathProgress = 0, + }; + + if (tickDistance != 0) + { + for (var span = 0; span < spanCount; span++) + { + var spanStartTime = startTime + span * spanDuration; + var reversed = span % 2 == 1; + + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d > length - minDistanceFromEnd) + break; + + var pathProgress = d / length; + var timeProgress = reversed ? 1 - pathProgress : pathProgress; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Tick, + SpanIndex = span, + SpanStartTime = spanStartTime, + StartTime = spanStartTime + timeProgress * spanDuration, + PathProgress = pathProgress, + }; + } + + if (span < spanCount - 1) + { + yield return new SliderEventDescriptor + { + Type = SliderEventType.Repeat, + SpanIndex = span, + SpanStartTime = startTime + span * spanDuration, + StartTime = spanStartTime + (span + 1) * spanDuration, + PathProgress = (span + 1) % 2, + }; + } + } + } + + double totalDuration = spanCount * spanDuration; + + var tail = new SliderEventDescriptor + { + Type = SliderEventType.Tail, + SpanIndex = spanCount - 1, + SpanStartTime = startTime + (spanCount - 1) * spanDuration, + StartTime = startTime + totalDuration, + PathProgress = 1, + }; + + if (legacyLastTickOffset != null) + tail.StartTime = Math.Max(startTime + totalDuration / 2, tail.StartTime - legacyLastTickOffset.Value); + + yield return tail; + } + } + + public struct SliderEventDescriptor + { + public SliderEventType Type; + + public int SpanIndex; + + public double SpanStartTime; + + public double StartTime; + + public double PathProgress; + } + + public enum SliderEventType + { + Tick, + Head, + Tail, + Repeat + } +} From 800007c3782b11ebce37a0ac695e763345b9a84e Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 8 Mar 2019 18:17:50 +0900 Subject: [PATCH 05/59] Set DummyWorkingBeatmap difficulties to 0 for better fallback display --- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 0aa1697bf8..f9df025be8 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -26,7 +26,12 @@ namespace osu.Game.Beatmaps Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty(), + BaseDifficulty = new BeatmapDifficulty + { + DrainRate = 0, + CircleSize = 0, + OverallDifficulty = 0, + }, Ruleset = new DummyRulesetInfo() }) { From 192c257aac669402ac70bd34f341badaac07ad36 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 8 Mar 2019 18:32:39 +0900 Subject: [PATCH 06/59] Add test steps for BeatmapDetailArea --- .../Visual/TestCaseBeatmapDetailArea.cs | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index c23075a127..ff39ad5c0d 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osuTK; @@ -14,12 +16,141 @@ namespace osu.Game.Tests.Visual { public TestCaseBeatmapDetailArea() { - Add(new BeatmapDetailArea + BeatmapDetailArea detailsArea; + Add(detailsArea = new BeatmapDetailArea { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(550f, 450f), }); + + AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + } + ); + + AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + }); + + AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "Only Ratings", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has ratings metrics but not retries or fails", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 6, + DrainRate = 9, + OverallDifficulty = 6, + ApproachRate = 6, + }, + StarDifficulty = 4.8f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + }, + } + }); + + AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "Only Retries and Fails", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has retries and fails but no ratings", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 3.7f, + DrainRate = 6, + OverallDifficulty = 6, + ApproachRate = 7, + }, + StarDifficulty = 2.91f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + }); + + AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "No Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has no metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5.5f, + ApproachRate = 6.5f, + }, + StarDifficulty = 1.97f, + } + }); + + AddStep("null beatmap", () => detailsArea.Beatmap = null); } } } From 8e5816805c11c67a3546952bfbef51a1dcdddfa0 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 8 Mar 2019 18:44:35 +0900 Subject: [PATCH 07/59] Fix showing outdated data for non-online beatmaps --- osu.Game/Screens/Select/BeatmapDetails.cs | 70 +++++++---------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 07db073edb..6057ba382b 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -175,21 +175,21 @@ namespace osu.Game.Screens.Select private void updateStatistics() { + advanced.Beatmap = Beatmap; + description.Text = Beatmap?.Version; + source.Text = Beatmap?.Metadata?.Source; + tags.Text = Beatmap?.Metadata?.Tags; + if (Beatmap == null) { - clearStats(); + ratingsContainer.FadeOut(transition_duration); + failRetryContainer.FadeOut(transition_duration); return; } - ratingsContainer.FadeIn(transition_duration); - advanced.Beatmap = Beatmap; - description.Text = Beatmap.Version; - source.Text = Beatmap.Metadata.Source; - tags.Text = Beatmap.Metadata.Tags; - - var requestedBeatmap = Beatmap; - if (requestedBeatmap.Metrics == null && requestedBeatmap.OnlineBeatmapID != null) + if (Beatmap.Metrics == null && Beatmap.OnlineBeatmapID != null) { + var requestedBeatmap = Beatmap; var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); lookup.Success += res => { @@ -198,39 +198,35 @@ namespace osu.Game.Screens.Select return; requestedBeatmap.Metrics = res; - Schedule(() => displayMetrics(res)); + Schedule(() => updateMetrics(res)); }; - lookup.Failure += e => Schedule(() => displayMetrics(null)); - + lookup.Failure += e => Schedule(() => updateMetrics(null)); api.Queue(lookup); loading.Show(); } - - displayMetrics(requestedBeatmap.Metrics, false); + else + { + updateMetrics(Beatmap.Metrics); + } } - private void displayMetrics(BeatmapMetrics metrics, bool failOnMissing = true) + private void updateMetrics(BeatmapMetrics metrics) { var hasRatings = metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); - if (failOnMissing) loading.Hide(); - if (hasRatings) { ratings.Metrics = metrics; - ratings.FadeIn(transition_duration); + ratingsContainer.FadeIn(transition_duration); } - else if (failOnMissing) + else { ratings.Metrics = new BeatmapMetrics { Ratings = new int[10], }; - } - else - { - ratings.FadeTo(0.25f, transition_duration); + ratingsContainer.FadeTo(0.25f, transition_duration); } if (hasRetriesFails) @@ -238,41 +234,17 @@ namespace osu.Game.Screens.Select failRetryGraph.Metrics = metrics; failRetryContainer.FadeIn(transition_duration); } - else if (failOnMissing) + else { failRetryGraph.Metrics = new BeatmapMetrics { Fails = new int[100], Retries = new int[100], }; + failRetryContainer.FadeOut(transition_duration); } - else - { - failRetryContainer.FadeTo(0.25f, transition_duration); - } - } - - private void clearStats() - { - description.Text = null; - source.Text = null; - tags.Text = null; - - advanced.Beatmap = new BeatmapInfo - { - StarDifficulty = 0, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 0, - DrainRate = 0, - OverallDifficulty = 0, - ApproachRate = 0, - }, - }; loading.Hide(); - ratingsContainer.FadeOut(transition_duration); - failRetryContainer.FadeOut(transition_duration); } private class DetailBox : Container From 355705f0a5e510d6c4bcd25b8ba86f9f8ffda4ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2019 19:57:30 +0900 Subject: [PATCH 08/59] Fix legacy tick handling --- .../Objects/JuiceStream.cs | 127 +++++++----------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Rulesets/Objects/SliderEventGenerator.cs | 35 +++-- 3 files changed, 75 insertions(+), 89 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 61bb4335f3..c5c9b09b89 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Audio; @@ -25,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects public double Velocity; public double TickDistance; + /// + /// The length of one span of this . + /// + public double SpanDuration => Duration / this.SpanCount(); + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -41,19 +45,6 @@ namespace osu.Game.Rulesets.Catch.Objects protected override void CreateNestedHitObjects() { base.CreateNestedHitObjects(); - createTicks(); - } - - private void createTicks() - { - if (TickDistance == 0) - return; - - var length = Path.Distance; - var tickDistance = Math.Min(TickDistance, length); - var spanDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; var tickSamples = Samples.Select(s => new SampleInfo { @@ -62,81 +53,57 @@ namespace osu.Game.Rulesets.Catch.Objects Volume = s.Volume }).ToList(); - AddNested(new Fruit + SliderEventDescriptor? lastEvent = null; + + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - Samples = Samples, - StartTime = StartTime, - X = X - }); - - double lastTickTime = StartTime; - - for (int span = 0; span < this.SpanCount(); span++) - { - var spanStartTime = StartTime + span * spanDuration; - var reversed = span % 2 == 1; - - for (double d = tickDistance;; d += tickDistance) + // generate tiny droplets since the last point + if (lastEvent != null) { - bool isLastTick = false; - if (d + minDistanceFromEnd >= length) + double sinceLastTick = e.StartTime - lastEvent.Value.StartTime; + + if (sinceLastTick > 80) { - d = length; - isLastTick = true; - } + double timeBetweenTiny = sinceLastTick; + while (timeBetweenTiny > 100) + timeBetweenTiny /= 2; - var timeProgress = d / length; - var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - - double time = spanStartTime + timeProgress * spanDuration; - - if (LegacyLastTickOffset != null) - { - // If we're the last tick, apply the legacy offset - if (span == this.SpanCount() - 1 && isLastTick) - time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value); - } - - int tinyTickCount = 1; - double tinyTickInterval = time - lastTickTime; - while (tinyTickInterval > 100 && tinyTickCount < 10000) - { - tinyTickInterval /= 2; - tinyTickCount *= 2; - } - - for (int tinyTickIndex = 0; tinyTickIndex < tinyTickCount - 1; tinyTickIndex++) - { - var t = lastTickTime + (tinyTickIndex + 1) * tinyTickInterval; - double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration; - - AddNested(new TinyDroplet + for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { - StartTime = t, - X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = tickSamples - }); + AddNested(new TinyDroplet + { + Samples = tickSamples, + StartTime = t + lastEvent.Value.StartTime, + X = X + Path.PositionAt( + lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH, + }); + } } - - lastTickTime = time; - - if (isLastTick) - break; - - AddNested(new Droplet - { - StartTime = time, - X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = tickSamples - }); } - AddNested(new Fruit + lastEvent = e; + + switch (e.Type) { - Samples = Samples, - StartTime = spanStartTime + spanDuration, - X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH - }); + case SliderEventType.Tick: + AddNested(new Droplet + { + Samples = tickSamples, + StartTime = e.StartTime, + X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + }); + break; + case SliderEventType.Head: + case SliderEventType.Tail: + case SliderEventType.Repeat: + AddNested(new Fruit + { + Samples = Samples, + StartTime = e.StartTime, + X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + }); + break; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9638d5e6f0..4ea8b00c32 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; - case SliderEventType.Tail: + case SliderEventType.LegacyFinalTick: AddNested(TailCircle = new SliderTailCircle(this) { StartTime = e.StartTime, diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 39827ef532..3fabe33b6a 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Objects Type = SliderEventType.Repeat, SpanIndex = span, SpanStartTime = startTime + span * spanDuration, - StartTime = spanStartTime + (span + 1) * spanDuration, + StartTime = spanStartTime + spanDuration, PathProgress = (span + 1) % 2, }; } @@ -70,19 +70,37 @@ namespace osu.Game.Rulesets.Objects double totalDuration = spanCount * spanDuration; - var tail = new SliderEventDescriptor + // Okay, I'll level with you. I made a mistake. It was 2007. + // Times were simpler. osu! was but in its infancy and sliders were a new concept. + // A hack was made, which has unfortunately lived through until this day. + // + // This legacy tick is used for some calculations and judgements where audio output is not required. + // Generally we are keeping this around just for difficulty compatibility. + // Optimistically we do not want to ever use this for anything user-facing going forwards. + + int finalSpanIndex = spanCount - 1; + double finalSpanStartTime = startTime + finalSpanIndex * spanDuration; + double finalSpanTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0)); + double finalProgress = (finalSpanTime - finalSpanStartTime) / spanDuration; + if (spanCount % 2 == 0) finalProgress = 1 - finalProgress; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.LegacyFinalTick, + SpanIndex = finalSpanIndex, + SpanStartTime = finalSpanStartTime, + StartTime = finalSpanTime, + PathProgress = finalProgress, + }; + + yield return new SliderEventDescriptor { Type = SliderEventType.Tail, SpanIndex = spanCount - 1, SpanStartTime = startTime + (spanCount - 1) * spanDuration, StartTime = startTime + totalDuration, - PathProgress = 1, + PathProgress = spanCount % 2, }; - - if (legacyLastTickOffset != null) - tail.StartTime = Math.Max(startTime + totalDuration / 2, tail.StartTime - legacyLastTickOffset.Value); - - yield return tail; } } @@ -102,6 +120,7 @@ namespace osu.Game.Rulesets.Objects public enum SliderEventType { Tick, + LegacyFinalTick, Head, Tail, Repeat From dd50c5dc1a2102cca5f7f967ccb7e4750a5b0c71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2019 19:57:39 +0900 Subject: [PATCH 09/59] Add player test for osu! ruleset --- osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs new file mode 100644 index 0000000000..9f13c19390 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseOsuPlayer : Game.Tests.Visual.TestCasePlayer + { + public TestCaseOsuPlayer() + : base(new OsuRuleset()) + { + } + } +} From 165a353a83b7daacf5ed913b004ab64982ef2069 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2019 20:12:48 +0900 Subject: [PATCH 10/59] Add extensive commenting about LegacyLastTick usage --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++++-- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index c5c9b09b89..9a5bad01fe 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Catch.Objects } } + // this also includes LegacyLastTick and this is used for TinyDroplet generation above. + // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied. lastEvent = e; switch (e.Type) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4ea8b00c32..5de36e1cff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osuTK; @@ -194,7 +194,9 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; - case SliderEventType.LegacyFinalTick: + // we need to use the LegacyLastTick here for compatibility reasons (difficulty). + // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. + // if this is to change, we should revisit this. AddNested(TailCircle = new SliderTailCircle(this) { StartTime = e.StartTime, diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 43a2ae0fbb..4f2af64161 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -8,6 +8,10 @@ using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects { + /// + /// Note that this should not be used for timing correctness. + /// See usage in for more information. + /// public class SliderTailCircle : SliderCircle { private readonly IBindable pathBindable = new Bindable(); From 93a999396e24f00bf048fb705365d28bcddb34c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2019 20:13:11 +0900 Subject: [PATCH 11/59] LegacyFinalTick -> LegacyLastTick to match existing variable --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 3 ++- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5de36e1cff..1efd92987d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osuTK; @@ -194,6 +194,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; + case SliderEventType.LegacyLastTick: // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. // if this is to change, we should revisit this. diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 3fabe33b6a..3f8927e740 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Objects yield return new SliderEventDescriptor { - Type = SliderEventType.LegacyFinalTick, + Type = SliderEventType.LegacyLastTick, SpanIndex = finalSpanIndex, SpanStartTime = finalSpanStartTime, StartTime = finalSpanTime, @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Objects public enum SliderEventType { Tick, - LegacyFinalTick, + LegacyLastTick, Head, Tail, Repeat From eadd7ce9138403f189d9ad06871dcce3c1d2d93f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Mar 2019 20:13:55 +0900 Subject: [PATCH 12/59] Update catch dfificulty test Likely the result of fixing https://github.com/ppy/osu/issues/4426. --- osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 01c57a6b9a..51fe0b035d 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase(4.2038001515546597d, "diffcalc-test")] + [TestCase(4.2058561036909863d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); From 7311ccabfbb18f9d53b5a695381022b3e05151e1 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 8 Mar 2019 20:27:50 -0800 Subject: [PATCH 13/59] Fix direct panel stats being misaligned by one pixel --- osu.Game/Overlays/Direct/DirectGridPanel.cs | 5 +---- osu.Game/Overlays/Direct/DirectListPanel.cs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 1413f0f885..b35dbde639 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -185,10 +185,7 @@ namespace osu.Game.Overlays.Direct Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, Children = new[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) - { - Margin = new MarginPadding { Right = 1 }, - }, + new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0), new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 01393ad98b..d857a0f042 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -160,10 +160,7 @@ namespace osu.Game.Overlays.Direct Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) - { - Margin = new MarginPadding { Right = 1 }, - }, + new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0), new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new FillFlowContainer { From 0fc6fa7245598b4b120c91b067e42e474ddb10f4 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 9 Mar 2019 20:29:56 -0800 Subject: [PATCH 14/59] Fix file naming on ParticipantCountDisplay --- .../{ParticipantCount.cs => ParticipantCountDisplay.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Screens/Multi/Components/{ParticipantCount.cs => ParticipantCountDisplay.cs} (100%) diff --git a/osu.Game/Screens/Multi/Components/ParticipantCount.cs b/osu.Game/Screens/Multi/Components/ParticipantCountDisplay.cs similarity index 100% rename from osu.Game/Screens/Multi/Components/ParticipantCount.cs rename to osu.Game/Screens/Multi/Components/ParticipantCountDisplay.cs From 2029cf93fd1962f8f0b01161d50264cd2a85575a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2019 14:33:21 +0900 Subject: [PATCH 15/59] Rename and reuse variables --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 3f8927e740..84621ba7a3 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -80,8 +80,9 @@ namespace osu.Game.Rulesets.Objects int finalSpanIndex = spanCount - 1; double finalSpanStartTime = startTime + finalSpanIndex * spanDuration; - double finalSpanTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0)); - double finalProgress = (finalSpanTime - finalSpanStartTime) / spanDuration; + double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0)); + double finalProgress = (finalSpanEndTime - finalSpanStartTime) / spanDuration; + if (spanCount % 2 == 0) finalProgress = 1 - finalProgress; yield return new SliderEventDescriptor @@ -89,14 +90,14 @@ namespace osu.Game.Rulesets.Objects Type = SliderEventType.LegacyLastTick, SpanIndex = finalSpanIndex, SpanStartTime = finalSpanStartTime, - StartTime = finalSpanTime, + StartTime = finalSpanEndTime, PathProgress = finalProgress, }; yield return new SliderEventDescriptor { Type = SliderEventType.Tail, - SpanIndex = spanCount - 1, + SpanIndex = finalSpanIndex, SpanStartTime = startTime + (spanCount - 1) * spanDuration, StartTime = startTime + totalDuration, PathProgress = spanCount % 2, From 489153579a7b8a15d5406485005b9d7ee80c2d7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2019 14:36:29 +0900 Subject: [PATCH 16/59] Add xmldoc and clarify struct variables --- .../Objects/JuiceStream.cs | 8 ++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++-- .../Rulesets/Objects/SliderEventGenerator.cs | 33 +++++++++++++++---- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 9a5bad01fe..2adc156efd 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Objects // generate tiny droplets since the last point if (lastEvent != null) { - double sinceLastTick = e.StartTime - lastEvent.Value.StartTime; + double sinceLastTick = e.Time - lastEvent.Value.Time; if (sinceLastTick > 80) { @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { Samples = tickSamples, - StartTime = t + lastEvent.Value.StartTime, + StartTime = t + lastEvent.Value.Time, X = X + Path.PositionAt( lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH, }); @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new Droplet { Samples = tickSamples, - StartTime = e.StartTime, + StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, }); break; @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new Fruit { Samples = Samples, - StartTime = e.StartTime, + StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, }); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1efd92987d..4a2b3ecb2d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Objects { SpanIndex = e.SpanIndex, SpanStartTime = e.SpanStartTime, - StartTime = e.StartTime, + StartTime = e.Time, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, @@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Objects case SliderEventType.Head: AddNested(HeadCircle = new SliderCircle { - StartTime = e.StartTime, + StartTime = e.Time, Position = Position, Samples = getNodeSamples(0), SampleControlPoint = SampleControlPoint, @@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.Osu.Objects // if this is to change, we should revisit this. AddNested(TailCircle = new SliderTailCircle(this) { - StartTime = e.StartTime, + StartTime = e.Time, Position = EndPosition, IndexInCurrentCombo = IndexInCurrentCombo, ComboIndex = ComboIndex, diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 84621ba7a3..99ad78eed1 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects Type = SliderEventType.Head, SpanIndex = 0, SpanStartTime = startTime, - StartTime = startTime, + Time = startTime, PathProgress = 0, }; @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Objects Type = SliderEventType.Tick, SpanIndex = span, SpanStartTime = spanStartTime, - StartTime = spanStartTime + timeProgress * spanDuration, + Time = spanStartTime + timeProgress * spanDuration, PathProgress = pathProgress, }; } @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Objects Type = SliderEventType.Repeat, SpanIndex = span, SpanStartTime = startTime + span * spanDuration, - StartTime = spanStartTime + spanDuration, + Time = spanStartTime + spanDuration, PathProgress = (span + 1) % 2, }; } @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Objects Type = SliderEventType.LegacyLastTick, SpanIndex = finalSpanIndex, SpanStartTime = finalSpanStartTime, - StartTime = finalSpanEndTime, + Time = finalSpanEndTime, PathProgress = finalProgress, }; @@ -99,22 +99,41 @@ namespace osu.Game.Rulesets.Objects Type = SliderEventType.Tail, SpanIndex = finalSpanIndex, SpanStartTime = startTime + (spanCount - 1) * spanDuration, - StartTime = startTime + totalDuration, + Time = startTime + totalDuration, PathProgress = spanCount % 2, }; } } + /// + /// Describes a point in time on a slider given special meaning. + /// Should be used by rulesets to visualise the slider. + /// public struct SliderEventDescriptor { + /// + /// The type of event. + /// public SliderEventType Type; + /// + /// The time of this event. + /// + public double Time; + + /// + /// The zero-based index of the span. In the case of repeat sliders, this will increase each repeat. + /// public int SpanIndex; + /// + /// The time at which the contained begins. + /// public double SpanStartTime; - public double StartTime; - + /// + /// The progress along the slider's at which this event occurs. + /// public double PathProgress; } From 63fea65c0c1f25aa77fcee286074ec2441fdbdd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2019 14:53:21 +0900 Subject: [PATCH 17/59] Clarify repeat index --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4a2b3ecb2d..1afbacc01e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, - Samples = getNodeSamples(1 + e.SpanIndex) + Samples = getNodeSamples(e.SpanIndex + 1) }); break; } diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 99ad78eed1..a0f9d0a481 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Objects public double Time; /// - /// The zero-based index of the span. In the case of repeat sliders, this will increase each repeat. + /// The zero-based index of the span. In the case of repeat sliders, this will increase after each . /// public int SpanIndex; From 3a8c32d41b83cf980eb778ab8a9162c32586ae0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2019 17:03:01 +0900 Subject: [PATCH 18/59] Add the ability for ArchiveModelManager to re-import even when existing entry is present --- osu.Game/Beatmaps/BeatmapManager.cs | 12 ++++++ osu.Game/Database/ArchiveModelManager.cs | 51 ++++++++++++++++++------ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 88f5e777e3..974c023a20 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -106,7 +106,10 @@ namespace osu.Game.Beatmaps foreach (BeatmapInfo b in beatmapSet.Beatmaps) fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps); + } + protected override void PreImport(BeatmapSetInfo beatmapSet) + { // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) { @@ -254,6 +257,15 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanUndelete(existing, import)) + return false; + + // force re-import if we are not in a sane state. + return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID; + } + /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9ec184abd7..3805921ac2 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -300,21 +300,31 @@ namespace osu.Game.Database { if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - var existing = CheckForExisting(item); - - if (existing != null) - { - Undelete(existing); - Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); - handleEvent(() => ItemAdded?.Invoke(existing, true)); - return existing; - } - if (archive != null) item.Files = createFileInfos(archive, Files); Populate(item, archive); + var existing = CheckForExisting(item); + + if (existing != null) + { + if (CanUndelete(existing, item)) + { + Undelete(existing); + Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); + handleEvent(() => ItemAdded?.Invoke(existing, true)); + return existing; + } + else + { + Delete(existing); + ModelStore.PurgeDeletable(s => s.ID == existing.ID); + } + } + + PreImport(item); + // import to store ModelStore.Add(item); } @@ -542,12 +552,29 @@ namespace osu.Game.Database { } + /// + /// Perform any final actions before the import to database executes. + /// + /// The model prepared for import. + protected virtual void PreImport(TModel model) + { + } + /// /// Check whether an existing model already exists for a new import item. /// - /// The new model proposed for import. Note that has not yet been run on this model. + /// The new model proposed for import. /// An existing model which matches the criteria to skip importing, else null. - protected virtual TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + + /// + /// After an existing is found during an import process, the default behaviour is to restore the existing + /// item and skip the import. This method allows changing that behaviour. + /// + /// The existing model. + /// The newly imported model. + /// Whether the existing model should be restored and used. Returning false will delete the existing a force a re-import. + protected virtual bool CanUndelete(TModel existing, TModel import) => true; private DbSet queryModel() => ContextFactory.Get().Set(); From d0ae75af6e9ef743f20d84f84f4466445e6aca08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2019 18:13:33 +0900 Subject: [PATCH 19/59] Add tests and fix scenario where all matching are contained by duplicate candidate --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 35 ++++++++++++++----- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 5b8bdd8a51..0f65f7f82e 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -207,6 +207,41 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [TestCase(true)] + [TestCase(false)] + public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}")) + { + try + { + var osu = loadOsu(host); + + var imported = LoadOszIntoOsu(osu); + + if (set) + imported.OnlineBeatmapSetID = 1234; + else + imported.Beatmaps.First().OnlineBeatmapID = 1234; + + osu.Dependencies.Get().Update(imported); + + deleteBeatmapSet(imported, osu); + + var importedSecondTime = LoadOszIntoOsu(osu); + + // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + host.Exit(); + } + } + } + [Test] [NonParallelizable] [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 974c023a20..711aa0b79b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -102,7 +102,7 @@ namespace osu.Game.Beatmaps b.BeatmapSet = beatmapSet; } - validateOnlineIds(beatmapSet.Beatmaps); + validateOnlineIds(beatmapSet); foreach (BeatmapInfo b in beatmapSet.Beatmaps) fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps); @@ -123,14 +123,30 @@ namespace osu.Game.Beatmaps } } - private void validateOnlineIds(List beatmaps) + private void validateOnlineIds(BeatmapSetInfo beatmapSet) { - var beatmapIds = beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); + var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - // ensure all IDs are unique in this set and none match existing IDs in the local beatmap store. - if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1) || QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).Any()) - // remove all online IDs if any problems were found. - beatmaps.ForEach(b => b.OnlineBeatmapID = null); + // ensure all IDs are unique + if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) + { + resetIds(); + return; + } + + // find any existing beatmaps in the database that have matching online ids + var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList(); + + if (existingBeatmaps.Count > 0) + { + // reset the import ids (to force a re-fetch) *unless* they match the candidate CheckForExisting set. + // we can ignore the case where the new ids are contained by the CheckForExisting set as it will either be used (import skipped) or deleted. + var existing = CheckForExisting(beatmapSet); + if (existing == null || existingBeatmaps.Any(b => !existing.Beatmaps.Contains(b))) + resetIds(); + } + + void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); } /// @@ -262,8 +278,11 @@ namespace osu.Game.Beatmaps if (!base.CanUndelete(existing, import)) return false; + var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + // force re-import if we are not in a sane state. - return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID; + return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); } /// From 59897bbeb5607ef5d96763bea87425545b04f6c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Mar 2019 18:45:30 +0900 Subject: [PATCH 20/59] Refactor UpdateableBeatmapBackground lookup logic for clarity Closes #4401. Alternative to #4439. --- .../UpdateableBeatmapBackgroundSprite.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index f0af09459f..ec75c1a1fb 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -45,24 +45,7 @@ namespace osu.Game.Beatmaps.Drawables protected override Drawable CreateDrawable(BeatmapInfo model) { - Drawable drawable; - - var localBeatmap = beatmaps.GetWorkingBeatmap(model); - - if (model?.BeatmapSet?.OnlineInfo != null) - { - drawable = new BeatmapSetCover(model.BeatmapSet, beatmapSetCoverType); - } - else if (localBeatmap.BeatmapInfo.ID != 0) - { - // Fall back to local background if one exists - drawable = new BeatmapBackgroundSprite(localBeatmap); - } - else - { - // Use the default background if somehow an online set does not exist and we don't have a local copy. - drawable = new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap); - } + Drawable drawable = getDrawableForModel(model); drawable.RelativeSizeAxes = Axes.Both; drawable.Anchor = Anchor.Centre; @@ -72,5 +55,16 @@ namespace osu.Game.Beatmaps.Drawables return drawable; } + + private Drawable getDrawableForModel(BeatmapInfo model) + { + // prefer online cover where available. + if (model?.BeatmapSet?.OnlineInfo != null) + return new BeatmapSetCover(model.BeatmapSet, beatmapSetCoverType); + + return model?.ID > 0 + ? new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(model)) + : new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap); + } } } From 941a72d69aa9b7c688885f89c9a51c3cf067101c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 01:34:31 +0900 Subject: [PATCH 21/59] Fix osu!direct firing excess queries during initial search characters Due to faulty debounce fire logic, a web request would always fire with a single character search, followed by the real search. This caused unwanted delays and display weirdness. --- osu.Game/Overlays/DirectOverlay.cs | 48 +++++++++++++----------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 0dc74b6a88..a3d25a7a1c 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -134,9 +134,9 @@ namespace osu.Game.Overlays Filter.Tabs.Current.Value = DirectSortCriteria.Ranked; } }; - ((FilterControl)Filter).Ruleset.ValueChanged += _ => Scheduler.AddOnce(updateSearch); + ((FilterControl)Filter).Ruleset.ValueChanged += _ => queueUpdateSearch(); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdateSearch(); Header.Tabs.Current.ValueChanged += tab => { @@ -144,24 +144,11 @@ namespace osu.Game.Overlays { currentQuery.Value = string.Empty; Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value; - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); } }; - currentQuery.ValueChanged += text => - { - queryChangedDebounce?.Cancel(); - - if (string.IsNullOrEmpty(text.NewValue)) - Scheduler.AddOnce(updateSearch); - else - { - BeatmapSets = null; - ResultAmounts = null; - - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); - } - }; + currentQuery.ValueChanged += text => queueUpdateSearch(!string.IsNullOrEmpty(text.NewValue)); currentQuery.BindTo(Filter.Search.Current); @@ -170,7 +157,7 @@ namespace osu.Game.Overlays if (Header.Tabs.Current.Value != DirectTab.Search && tab.NewValue != (DirectSortCriteria)Header.Tabs.Current.Value) Header.Tabs.Current.Value = DirectTab.Search; - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); }; updateResultCounts(); @@ -242,37 +229,42 @@ namespace osu.Game.Overlays // Queries are allowed to be run only on the first pop-in if (getSetsRequest == null) - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); } private SearchBeatmapSetsRequest getSetsRequest; - private readonly Bindable currentQuery = new Bindable(); + private readonly Bindable currentQuery = new Bindable(string.Empty); private ScheduledDelegate queryChangedDebounce; private PreviewTrackManager previewTrackManager; + private void queueUpdateSearch(bool queryTextChanged = false) + { + BeatmapSets = null; + ResultAmounts = null; + + getSetsRequest?.Cancel(); + + queryChangedDebounce?.Cancel(); + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); + } + private void updateSearch() { - queryChangedDebounce?.Cancel(); - if (!IsLoaded) return; if (State == Visibility.Hidden) return; - BeatmapSets = null; - ResultAmounts = null; - - getSetsRequest?.Cancel(); - if (api == null) return; previewTrackManager.StopAnyPlaying(this); - getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, + getSetsRequest = new SearchBeatmapSetsRequest( + currentQuery.Value, ((FilterControl)Filter).Ruleset.Value, Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.Tabs.Current.Value); //todo: sort direction (?) From 38e75421ab663e63343b9d1121541c2358d1b2b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 02:21:34 +0900 Subject: [PATCH 22/59] Fix HoldForMenuButton getting stuck in a confirming state Closes #4446. --- .../Screens/Play/HUD/HoldForMenuButton.cs | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index ca4cce8929..8bef15a9f1 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -92,30 +92,6 @@ namespace osu.Game.Screens.Play.HUD public Action HoverGained; public Action HoverLost; - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - BeginConfirm(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - AbortConfirm(); - return true; - } - - return false; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -178,7 +154,7 @@ namespace osu.Game.Screens.Play.HUD // avoid starting a new confirm call until we finish animating. pendingAnimation = true; - Progress.Value = 0; + AbortConfirm(); overlayCircle.ScaleTo(0, 100) .Then().FadeOut().ScaleTo(1).FadeIn(500) @@ -207,6 +183,32 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + if (!pendingAnimation) + BeginConfirm(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + AbortConfirm(); + return true; + } + + return false; + } + + protected override bool OnMouseDown(MouseDownEvent e) { if (!pendingAnimation && e.CurrentState.Mouse.Buttons.Count() == 1) From 1be4c7b813d423360d25e3eef3de857add501ded Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 02:23:23 +0900 Subject: [PATCH 23/59] Fix excess newline --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 8bef15a9f1..50bc34726a 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -208,7 +208,6 @@ namespace osu.Game.Screens.Play.HUD return false; } - protected override bool OnMouseDown(MouseDownEvent e) { if (!pendingAnimation && e.CurrentState.Mouse.Buttons.Count() == 1) From 13b3036ec6c6d21b5b2278010ac184efe09f4e8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 02:45:45 +0900 Subject: [PATCH 24/59] Fix "wind" mods adjusting rate twice This is a hotfix for incorrect framework behaviour. Closes #4442 --- osu.Game/Screens/Play/GameplayClockContainer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 594df63420..0a6453beeb 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -141,11 +141,10 @@ namespace osu.Game.Screens.Play { if (sourceClock == null) return; - sourceClock.Rate = 1; + sourceClock.Rate = UserPlaybackRate.Value; + foreach (var mod in beatmap.Mods.Value.OfType()) mod.ApplyToClock(sourceClock); - - sourceClock.Rate *= UserPlaybackRate.Value; } } } From cdeaa80fea548a2be72ef7675976e73d0fed9f65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 11:35:25 +0900 Subject: [PATCH 25/59] Call ResetSpeedAdjustments before adjusting clock --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0a6453beeb..207627140d 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -141,6 +141,7 @@ namespace osu.Game.Screens.Play { if (sourceClock == null) return; + sourceClock.ResetSpeedAdjustments(); sourceClock.Rate = UserPlaybackRate.Value; foreach (var mod in beatmap.Mods.Value.OfType()) From ad3b956cec4fdfad8588a30a5c62846f9ae5337d Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 11 Mar 2019 20:10:59 -0700 Subject: [PATCH 26/59] Fix channel selection overlay dimming bg forever when popped out --- osu.Game/Overlays/ChatOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 5428279325..77f88ab4e7 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -320,6 +320,8 @@ namespace osu.Game.Overlays this.MoveToY(Height, transition_length, Easing.InSine); this.FadeOut(transition_length, Easing.InSine); + channelSelectionOverlay.State = Visibility.Hidden; + textbox.HoldFocus = false; base.PopOut(); } From c1a356161f08749b75bd3338206557f296bf1b6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 18:14:01 +0900 Subject: [PATCH 27/59] Allow use rate adjust to fallback to clock rate adjust --- osu.Game/Screens/Play/GameplayClockContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 207627140d..3c2cec1d94 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -142,7 +143,11 @@ namespace osu.Game.Screens.Play if (sourceClock == null) return; sourceClock.ResetSpeedAdjustments(); - sourceClock.Rate = UserPlaybackRate.Value; + + if (sourceClock is IHasTempoAdjust tempo) + tempo.TempoAdjust = UserPlaybackRate.Value; + else + sourceClock.Rate = UserPlaybackRate.Value; foreach (var mod in beatmap.Mods.Value.OfType()) mod.ApplyToClock(sourceClock); From 6832e384a9a63c050b5e66d8b702aa12633ef1e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 18:14:41 +0900 Subject: [PATCH 28/59] Simplify DT/HT/NC/DC --- osu.Game/Rulesets/Mods/ModDaycore.cs | 2 +- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 12 +++++------- osu.Game/Rulesets/Mods/ModHalfTime.cs | 12 +++++------- osu.Game/Rulesets/Mods/ModNightcore.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeAdjust.cs | 24 ++++++++++++++++++++++++ 5 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModTimeAdjust.cs diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 2eea6a237c..dded688e80 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToClock(IAdjustableClock clock) { if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust = 0.75; + pitchAdjust.PitchAdjust *= RateAdjust; else base.ApplyToClock(clock); } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index e59654c60d..9ea9eb76bc 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Timing; +using System.Linq; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModDoubleTime : Mod, IApplicableToClock + public abstract class ModDoubleTime : ModTimeAdjust, IApplicableToClock { public override string Name => "Double Time"; public override string Acronym => "DT"; @@ -15,11 +15,9 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime), typeof(ModTimeRamp) }; - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 1.5; - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); + + protected override double RateAdjust => 1.5; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 07cceb6f49..fe26c96214 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Timing; +using System.Linq; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHalfTime : Mod, IApplicableToClock + public abstract class ModHalfTime : ModTimeAdjust, IApplicableToClock { public override string Name => "Half Time"; public override string Acronym => "HT"; @@ -15,11 +15,9 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom..."; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime), typeof(ModTimeRamp) }; - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 0.75; - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); + + protected override double RateAdjust => 0.75; } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index e6bd532f72..a689292ed7 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToClock(IAdjustableClock clock) { if (clock is IHasPitchAdjust pitchAdjust) - pitchAdjust.PitchAdjust = 1.5; + pitchAdjust.PitchAdjust *= RateAdjust; else base.ApplyToClock(clock); } diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs new file mode 100644 index 0000000000..513883f552 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio; +using osu.Framework.Timing; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModTimeAdjust : Mod + { + public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; + + protected abstract double RateAdjust { get; } + + public virtual void ApplyToClock(IAdjustableClock clock) + { + if (clock is IHasTempoAdjust tempo) + tempo.TempoAdjust *= RateAdjust; + else + clock.Rate *= RateAdjust; + } + } +} From a10cd2288dc4433196a931ee5a85dd4a6a8e909a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 18:15:18 +0900 Subject: [PATCH 29/59] Improve ModRamp's handling of external rate changes --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 30 ++++++++++++++++++++------- osu.Game/Rulesets/Mods/ModWindDown.cs | 5 +++++ osu.Game/Rulesets/Mods/ModWindUp.cs | 7 ++++++- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 4a0ed0f9bb..1573418d95 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModTimeRamp : Mod { - public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime), typeof(ModHalfTime) }; + public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) }; protected abstract double FinalRateAdjustment { get; } } @@ -29,20 +29,26 @@ namespace osu.Game.Rulesets.Mods private IAdjustableClock clock; - private IHasPitchAdjust pitchAdjust; - /// /// The point in the beatmap at which the final ramping rate should be reached. /// private const double final_rate_progress = 0.75f; + /// + /// The adjustment applied on entering this mod's application method. + /// + private double baseAdjust; + public virtual void ApplyToClock(IAdjustableClock clock) { this.clock = clock; - pitchAdjust = (IHasPitchAdjust)clock; - // for preview purposes - pitchAdjust.PitchAdjust = 1.0 + FinalRateAdjustment; + // we capture the adjustment applied before entering our application method. + // this will cover external changes, which should re-fire this method. + baseAdjust = (clock as IHasPitchAdjust)?.PitchAdjust ?? clock.Rate; + + // for preview purposes. during gameplay, Update will overwrite this setting. + applyAdjustment(1); } public virtual void ApplyToBeatmap(Beatmap beatmap) @@ -56,9 +62,17 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { var absRate = Math.Abs(FinalRateAdjustment); - var adjustment = MathHelper.Clamp(absRate * ((clock.CurrentTime - beginRampTime) / finalRateTime), 0, absRate); + applyAdjustment(MathHelper.Clamp(absRate * ((clock.CurrentTime - beginRampTime) / finalRateTime), 0, absRate)); + } - pitchAdjust.PitchAdjust = 1 + Math.Sign(FinalRateAdjustment) * adjustment; + private void applyAdjustment(double adjustment) + { + var localAdjust = 1 + Math.Sign(FinalRateAdjustment) * adjustment; + + if (clock is IHasPitchAdjust tempo) + tempo.PitchAdjust = baseAdjust * localAdjust; + else + clock.Rate *= baseAdjust * localAdjust; } } } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 646c5c64e4..174070eb85 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; @@ -14,6 +16,9 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Sloooow doooown..."; public override FontAwesome Icon => FontAwesome.fa_chevron_circle_down; public override double ScoreMultiplier => 1.0; + protected override double FinalRateAdjustment => -0.25; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 9050b5591a..bf9af8a51d 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; @@ -14,6 +16,9 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Can you keep up?"; public override FontAwesome Icon => FontAwesome.fa_chevron_circle_up; public override double ScoreMultiplier => 1.0; + protected override double FinalRateAdjustment => 0.5; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); } -} \ No newline at end of file +} From 12f334035fb1b52d3328671ef270f8a591f2b760 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 19:23:24 +0900 Subject: [PATCH 30/59] Create properties for use in DrawableManiaJudgement --- .../UI/DrawableManiaJudgement.cs | 22 +++++++--------- .../Rulesets/Judgements/DrawableJudgement.cs | 26 +++++++++++++------ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 5874bac7f6..3073b1dd19 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -22,22 +22,20 @@ namespace osu.Game.Rulesets.Mania.UI JudgementText.Font = JudgementText.Font.With(size: 25); } + protected override double FadeInDuration => 50; + + protected override float InitialHitScale => 0.8f; + + protected override double HitFadeOutDuration => 200; + + protected override float HitScaleDuration => 250; + protected override void LoadComplete() { - base.LoadComplete(); - - this.FadeInFromZero(50, Easing.OutQuint); - if (Result.IsHit) - { - JudgementBody.ScaleTo(0.8f); - JudgementBody.ScaleTo(1, 250, Easing.OutElastic); + JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, HitScaleDuration); - JudgementBody.Delay(50).ScaleTo(0.75f, 250); - this.Delay(50).FadeOut(200); - } - - Expire(); + base.LoadComplete(); } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 0d6e11c649..e25676700d 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -32,6 +32,14 @@ namespace osu.Game.Rulesets.Judgements protected Container JudgementBody; protected SpriteText JudgementText; + protected virtual double FadeInDuration => 100; + + protected virtual float InitialHitScale => 0.9f; + + protected virtual float HitScaleDuration => 500; + + protected virtual double HitFadeOutDuration => 400; + /// /// Creates a drawable which visualises a . /// @@ -65,11 +73,13 @@ namespace osu.Game.Rulesets.Judgements }; } + private const double MISS_ANIMATION_DURATION = 800; + protected override void LoadComplete() { base.LoadComplete(); - this.FadeInFromZero(100, Easing.OutQuint); + this.FadeInFromZero(FadeInDuration, Easing.OutQuint); switch (Result.Type) { @@ -77,18 +87,18 @@ namespace osu.Game.Rulesets.Judgements break; case HitResult.Miss: JudgementBody.ScaleTo(1.6f); - JudgementBody.ScaleTo(1, 100, Easing.In); + JudgementBody.ScaleTo(1, FadeInDuration, Easing.In); - JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - JudgementBody.RotateTo(40, 800, Easing.InQuint); + JudgementBody.MoveToOffset(new Vector2(0, 100), MISS_ANIMATION_DURATION, Easing.InQuint); + JudgementBody.RotateTo(40, MISS_ANIMATION_DURATION, Easing.InQuint); - this.Delay(600).FadeOut(200); + this.Delay(500).FadeOut(200); break; default: - JudgementBody.ScaleTo(0.9f); - JudgementBody.ScaleTo(1, 500, Easing.OutElastic); + JudgementBody.ScaleTo(InitialHitScale); + JudgementBody.ScaleTo(1, HitScaleDuration, Easing.OutElastic); - this.Delay(100).FadeOut(400); + this.Delay(FadeInDuration).FadeOut(HitFadeOutDuration); break; } From 9850122245670545d1dde1dd5df132423fb07f2e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 19:24:46 +0900 Subject: [PATCH 31/59] Fix incorrect delay --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index e25676700d..308a2b00f9 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Judgements JudgementBody.MoveToOffset(new Vector2(0, 100), MISS_ANIMATION_DURATION, Easing.InQuint); JudgementBody.RotateTo(40, MISS_ANIMATION_DURATION, Easing.InQuint); - this.Delay(500).FadeOut(200); + this.Delay(600).FadeOut(200); break; default: JudgementBody.ScaleTo(InitialHitScale); From 3b3b1e51ee43953bf713768ac89cca5e3b6e2d31 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 19:41:33 +0900 Subject: [PATCH 32/59] Move hit animation logic into virtual method --- .../UI/DrawableManiaJudgement.cs | 15 ++++------- .../Rulesets/Judgements/DrawableJudgement.cs | 27 ++++++++++--------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 3073b1dd19..8797f014df 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -24,18 +24,13 @@ namespace osu.Game.Rulesets.Mania.UI protected override double FadeInDuration => 50; - protected override float InitialHitScale => 0.8f; - - protected override double HitFadeOutDuration => 200; - - protected override float HitScaleDuration => 250; - - protected override void LoadComplete() + protected override void ApplyHitAnimations() { - if (Result.IsHit) - JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, HitScaleDuration); + JudgementBody.ScaleTo(0.8f); + JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - base.LoadComplete(); + JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250); + this.Delay(FadeInDuration).FadeOut(200); } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 308a2b00f9..16e7fd4a15 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -32,14 +32,12 @@ namespace osu.Game.Rulesets.Judgements protected Container JudgementBody; protected SpriteText JudgementText; + /// + /// The amount of time for this judgement to initially fade in. + /// + /// Override this to change fade in times of mode-specific judgements protected virtual double FadeInDuration => 100; - protected virtual float InitialHitScale => 0.9f; - - protected virtual float HitScaleDuration => 500; - - protected virtual double HitFadeOutDuration => 400; - /// /// Creates a drawable which visualises a . /// @@ -73,7 +71,13 @@ namespace osu.Game.Rulesets.Judgements }; } - private const double MISS_ANIMATION_DURATION = 800; + protected virtual void ApplyHitAnimations() + { + JudgementBody.ScaleTo(0.9f); + JudgementBody.ScaleTo(1, 500, Easing.OutElastic); + + this.Delay(FadeInDuration).FadeOut(400); + } protected override void LoadComplete() { @@ -89,16 +93,13 @@ namespace osu.Game.Rulesets.Judgements JudgementBody.ScaleTo(1.6f); JudgementBody.ScaleTo(1, FadeInDuration, Easing.In); - JudgementBody.MoveToOffset(new Vector2(0, 100), MISS_ANIMATION_DURATION, Easing.InQuint); - JudgementBody.RotateTo(40, MISS_ANIMATION_DURATION, Easing.InQuint); + JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + JudgementBody.RotateTo(40, 800, Easing.InQuint); this.Delay(600).FadeOut(200); break; default: - JudgementBody.ScaleTo(InitialHitScale); - JudgementBody.ScaleTo(1, HitScaleDuration, Easing.OutElastic); - - this.Delay(FadeInDuration).FadeOut(HitFadeOutDuration); + ApplyHitAnimations(); break; } From 7eb253b6872acc10ad646f1e3d184cef0b51bbf5 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 19:44:53 +0900 Subject: [PATCH 33/59] Fix incorrect scale duration --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 16e7fd4a15..b273faec8f 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Judgements break; case HitResult.Miss: JudgementBody.ScaleTo(1.6f); - JudgementBody.ScaleTo(1, FadeInDuration, Easing.In); + JudgementBody.ScaleTo(1, 100, Easing.In); JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); JudgementBody.RotateTo(40, 800, Easing.InQuint); From 527b77b07de94ac2477923b5f27bab29ea7e899a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 19:52:44 +0900 Subject: [PATCH 34/59] Make all inheritors of drawablejudgement use new method --- .../Objects/Drawables/DrawableOsuJudgement.cs | 9 +++------ osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs | 8 +++----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 2512e74da7..938a2293ba 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -16,12 +15,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } - protected override void LoadComplete() + protected override void ApplyHitAnimations() { - if (Result.Type != HitResult.Miss) - JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); - - base.LoadComplete(); + JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + base.ApplyHitAnimations(); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 90841f11f5..943adaed4b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -39,12 +39,10 @@ namespace osu.Game.Rulesets.Taiko.UI } } - protected override void LoadComplete() + protected override void ApplyHitAnimations() { - if (Result.IsHit) - this.MoveToY(-100, 500); - - base.LoadComplete(); + this.MoveToY(-100, 500); + base.ApplyHitAnimations(); } } } From cc09ecbfcf27d2d220b8828f527bb1994444c341 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 20:31:15 +0900 Subject: [PATCH 35/59] Increase validation performed on .osu files to avoid hard crashes --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +- osu.Game/Beatmaps/Formats/LegacyParser.cs | 45 +++++++++++++++++++ .../Objects/Legacy/ConvertHitObjectParser.cs | 35 ++++++++------- 3 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Beatmaps/Formats/LegacyParser.cs diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 4f19fbd323..5988b05084 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -280,8 +280,8 @@ namespace osu.Game.Beatmaps.Formats { string[] split = line.Split(','); - double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo)); - double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); + double beatLength = Parsing.ParseDouble(split[1].Trim()); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; diff --git a/osu.Game/Beatmaps/Formats/LegacyParser.cs b/osu.Game/Beatmaps/Formats/LegacyParser.cs new file mode 100644 index 0000000000..777a85dc93 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/LegacyParser.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; + +namespace osu.Game.Beatmaps.Formats +{ + /// + /// Helper methods to parse from string to number and perform very basic validation. + /// + public static class Parsing + { + public const int MAX_COORDINATE_VALUE = 65536; + + public const double MAX_PARSE_VALUE = int.MaxValue; + + public static double ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE) + { + var output = float.Parse(input, CultureInfo.InvariantCulture); + if (output < -parseLimit) throw new FormatException("Value is too low"); + if (output > parseLimit) throw new FormatException("Value is too high"); + + return output; + } + + public static double ParseDouble(string input, double parseLimit = MAX_PARSE_VALUE) + { + var output = double.Parse(input, CultureInfo.InvariantCulture); + if (output < -parseLimit) throw new FormatException("Value is too low"); + if (output > parseLimit) throw new FormatException("Value is too high"); + + return output; + } + + public static int ParseInt(string input, int parseLimit = (int)MAX_PARSE_VALUE) + { + var output = int.Parse(input, CultureInfo.InvariantCulture); + if (output < -parseLimit) throw new FormatException("Value is too low"); + if (output > parseLimit) throw new FormatException("Value is too high"); + + return output; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index e397139843..4d36b45989 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -5,7 +5,6 @@ using osuTK; using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using osu.Game.Beatmaps.Formats; using osu.Game.Audio; @@ -46,9 +45,11 @@ namespace osu.Game.Rulesets.Objects.Legacy { string[] split = text.Split(','); - Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture)); + Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); - ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]); + double startTime = Parsing.ParseDouble(split[2]) + Offset; + + ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]); int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; type &= ~ConvertHitObjectType.ComboOffset; @@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Objects.Legacy bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); type &= ~ConvertHitObjectType.NewCombo; - var soundType = (LegacySoundType)int.Parse(split[4]); + var soundType = (LegacySoundType)Parsing.ParseInt(split[4]); var bankInfo = new SampleBankInfo(); HitObject result = null; @@ -107,7 +108,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } string[] temp = t.Split(':'); - points[pointIndex++] = new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)) - pos; + points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; } // osu-stable special-cased colinear perfect curves to a CurveType.Linear @@ -116,7 +117,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) pathType = PathType.Linear; - int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); + int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); @@ -125,7 +126,7 @@ namespace osu.Game.Rulesets.Objects.Legacy repeatCount = Math.Max(0, repeatCount - 1); if (split.Length > 7) - length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); + length = Math.Max(0, Parsing.ParseDouble(split[7])); if (split.Length > 10) readCustomSampleBanks(split[10], bankInfo); @@ -184,7 +185,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + Offset); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -193,12 +196,12 @@ namespace osu.Game.Rulesets.Objects.Legacy { // Note: Hold is generated by BMS converts - double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + double endTime = Math.Max(0, Parsing.ParseDouble(split[2])); if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) { string[] ss = split[5].Split(':'); - endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); + endTime = Parsing.ParseDouble(ss[0]); readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } @@ -211,7 +214,7 @@ namespace osu.Game.Rulesets.Objects.Legacy return null; } - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset; + result.StartTime = startTime; if (result.Samples.Count == 0) result.Samples = convertSoundType(soundType, bankInfo); @@ -222,7 +225,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } catch (FormatException) { - throw new FormatException("One or more hit objects were malformed."); + return null; } } @@ -233,8 +236,8 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); - var bank = (LegacyBeatmapDecoder.LegacySampleBank)int.Parse(split[0]); - var addbank = (LegacyBeatmapDecoder.LegacySampleBank)int.Parse(split[1]); + var bank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[0]); + var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[1]); string stringBank = bank.ToString().ToLowerInvariant(); if (stringBank == @"none") @@ -247,10 +250,10 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; if (split.Length > 2) - bankInfo.CustomSampleBank = int.Parse(split[2]); + bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]); if (split.Length > 3) - bankInfo.Volume = int.Parse(split[3]); + bankInfo.Volume = Parsing.ParseInt(split[3]); bankInfo.Filename = split.Length > 4 ? split[4] : null; } From 73d9aef5bdf70ee6c359a4909bb70cb29d2302ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 20:38:04 +0900 Subject: [PATCH 36/59] Fix filename --- osu.Game/Beatmaps/Formats/{LegacyParser.cs => Parsing.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Beatmaps/Formats/{LegacyParser.cs => Parsing.cs} (100%) diff --git a/osu.Game/Beatmaps/Formats/LegacyParser.cs b/osu.Game/Beatmaps/Formats/Parsing.cs similarity index 100% rename from osu.Game/Beatmaps/Formats/LegacyParser.cs rename to osu.Game/Beatmaps/Formats/Parsing.cs From 93b774104afab8644d8736d97549d391b2932f78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 11:30:33 +0900 Subject: [PATCH 37/59] Fix NaN handling --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 5 ++++- osu.Game/Beatmaps/Formats/Parsing.cs | 21 ++++++++++++------- .../Objects/Legacy/ConvertHitObjectParser.cs | 6 +++++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 5988b05084..84bd953068 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -348,7 +348,10 @@ namespace osu.Game.Beatmaps.Formats CustomSampleBank = customSampleBank }); } - catch (FormatException e) + catch (FormatException) + { + } + catch (OverflowException) { } } diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs index 777a85dc93..c3efb8c760 100644 --- a/osu.Game/Beatmaps/Formats/Parsing.cs +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -15,11 +15,14 @@ namespace osu.Game.Beatmaps.Formats public const double MAX_PARSE_VALUE = int.MaxValue; - public static double ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE) + public static float ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE) { var output = float.Parse(input, CultureInfo.InvariantCulture); - if (output < -parseLimit) throw new FormatException("Value is too low"); - if (output > parseLimit) throw new FormatException("Value is too high"); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + if (float.IsNaN(output)) throw new FormatException("Not a number"); return output; } @@ -27,8 +30,11 @@ namespace osu.Game.Beatmaps.Formats public static double ParseDouble(string input, double parseLimit = MAX_PARSE_VALUE) { var output = double.Parse(input, CultureInfo.InvariantCulture); - if (output < -parseLimit) throw new FormatException("Value is too low"); - if (output > parseLimit) throw new FormatException("Value is too high"); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + if (double.IsNaN(output)) throw new FormatException("Not a number"); return output; } @@ -36,8 +42,9 @@ namespace osu.Game.Beatmaps.Formats public static int ParseInt(string input, int parseLimit = (int)MAX_PARSE_VALUE) { var output = int.Parse(input, CultureInfo.InvariantCulture); - if (output < -parseLimit) throw new FormatException("Value is too low"); - if (output > parseLimit) throw new FormatException("Value is too high"); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); return output; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 4d36b45989..d738b1028e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -225,8 +225,12 @@ namespace osu.Game.Rulesets.Objects.Legacy } catch (FormatException) { - return null; } + catch (OverflowException) + { + } + + return null; } private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) From 0241148385c3687e28c717bdc13b772e787e2223 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 11:30:38 +0900 Subject: [PATCH 38/59] Add extensive tests --- .../Beatmaps/Formats/ParsingTest.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs diff --git a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs new file mode 100644 index 0000000000..b3863bcf44 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; +using NUnit.Framework; +using osu.Game.Beatmaps.Formats; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class ParsingTest + { + [Test] + public void TestNaNHandling() => allThrow("NaN"); + + [Test] + public void TestBadStringHandling() => allThrow("Random string 123"); + + [TestCase(Parsing.MAX_PARSE_VALUE)] + [TestCase(-1)] + [TestCase(0)] + [TestCase(1)] + [TestCase(-Parsing.MAX_PARSE_VALUE)] + [TestCase(10, 10)] + [TestCase(-10, 10)] + public void TestValidRanges(double input, double limit = Parsing.MAX_PARSE_VALUE) + { + Assert.AreEqual(Parsing.ParseInt((input).ToString(CultureInfo.InvariantCulture), (int)limit), (int)input); + Assert.AreEqual(Parsing.ParseFloat((input).ToString(CultureInfo.InvariantCulture), (float)limit), (float)input); + Assert.AreEqual(Parsing.ParseDouble((input).ToString(CultureInfo.InvariantCulture), limit), input); + } + + [TestCase(double.PositiveInfinity)] + [TestCase(double.NegativeInfinity)] + [TestCase(999999999999)] + [TestCase(Parsing.MAX_PARSE_VALUE * 1.1)] + [TestCase(-Parsing.MAX_PARSE_VALUE * 1.1)] + [TestCase(11, 10)] + [TestCase(-11, 10)] + public void TestOutOfRangeHandling(double input, double limit = Parsing.MAX_PARSE_VALUE) + => allThrow(input.ToString(CultureInfo.InvariantCulture), limit); + + private void allThrow(string input, double limit = Parsing.MAX_PARSE_VALUE) + where T : Exception + { + Assert.Throws(getIntParseException(input) ?? typeof(T), () => Parsing.ParseInt(input, (int)limit)); + Assert.Throws(() => Parsing.ParseFloat(input, (float)limit)); + Assert.Throws(() => Parsing.ParseDouble(input, limit)); + } + + /// + /// may not be able to parse some inputs. + /// In this case we expect to receive the raw parsing exception. + /// + /// The input attempting to be parsed. + /// The type of exception thrown by . Null if no exception is thrown. + private Type getIntParseException(string input) + { + try + { + var _ = int.Parse(input); + } + catch (Exception e) + { + return e.GetType(); + } + + return null; + } + } +} From a35d9ccc091c215e92087273eaf1ae498cc529c1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 13 Mar 2019 12:35:05 +0900 Subject: [PATCH 39/59] Apply clamping suggestion Co-Authored-By: peppy --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d738b1028e..791de05bf7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]); if (split.Length > 3) - bankInfo.Volume = Parsing.ParseInt(split[3]); + bankInfo.Volume = Math.Max(0, Parsing.ParseInt(split[3])); bankInfo.Filename = split.Length > 4 ? split[4] : null; } From f8b5cf8c8e51741a2feb4d4b56f44b43183dcca4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 13:46:41 +0900 Subject: [PATCH 40/59] Add extra clamping of endTime to startTime --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 791de05bf7..73b3e823fd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -196,12 +196,12 @@ namespace osu.Game.Rulesets.Objects.Legacy { // Note: Hold is generated by BMS converts - double endTime = Math.Max(0, Parsing.ParseDouble(split[2])); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2])); if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) { string[] ss = split[5].Split(':'); - endTime = Parsing.ParseDouble(ss[0]); + endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } From f4c505709ab2a9403ce88a5254e2104a9a9c823f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 13:56:31 +0900 Subject: [PATCH 41/59] Use parsing methods in more places Limiting scope to beatmap decoding for this pass. Can expand to skin/storyboard in the future. --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 84bd953068..9b3607029f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Globalization; using System.IO; using System.Linq; using osu.Framework.IO.File; @@ -25,7 +24,7 @@ namespace osu.Game.Beatmaps.Formats public static void Register() { - AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); + AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(Parsing.ParseInt(m.Split('v').Last()))); } /// @@ -104,25 +103,25 @@ namespace osu.Game.Beatmaps.Formats metadata.AudioFile = FileSafety.PathStandardise(pair.Value); break; case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); + beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); break; case @"PreviewTime": - metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value)); + metadata.PreviewTime = getOffsetTime(Parsing.ParseInt(pair.Value)); break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.Countdown = Parsing.ParseInt(pair.Value) == 1; break; case @"SampleSet": defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; case @"SampleVolume": - defaultSampleVolume = int.Parse(pair.Value); + defaultSampleVolume = Parsing.ParseInt(pair.Value); break; case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); + beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); switch (beatmap.BeatmapInfo.RulesetID) { @@ -142,13 +141,13 @@ namespace osu.Game.Beatmaps.Formats break; case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; } } @@ -163,16 +162,16 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); + beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); break; case @"GridSize": - beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); + beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); break; case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; } } @@ -209,10 +208,10 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); + beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); break; case @"BeatmapSetID": - beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = int.Parse(pair.Value) }; + beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; break; } } @@ -225,22 +224,22 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; case @"CircleSize": - difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.CircleSize = Parsing.ParseFloat(pair.Value); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); break; case @"ApproachRate": - difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); break; case @"SliderMultiplier": - difficulty.SliderMultiplier = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; case @"SliderTickRate": - difficulty.SliderTickRate = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; } } @@ -260,10 +259,12 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); break; case EventType.Break: + double start = getOffsetTime(Parsing.ParseDouble(split[1])); + var breakEvent = new BreakPeriod { - StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)), - EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo)) + StartTime = start, + EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[1]))) }; if (!breakEvent.HasEffect) @@ -286,19 +287,19 @@ namespace osu.Game.Beatmaps.Formats TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); LegacySampleBank sampleSet = defaultSampleBank; if (split.Length >= 4) - sampleSet = (LegacySampleBank)int.Parse(split[3]); + sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]); int customSampleBank = 0; if (split.Length >= 5) - customSampleBank = int.Parse(split[4]); + customSampleBank = Parsing.ParseInt(split[4]); int sampleVolume = defaultSampleVolume; if (split.Length >= 6) - sampleVolume = int.Parse(split[5]); + sampleVolume = Parsing.ParseInt(split[5]); bool timingChange = true; if (split.Length >= 7) @@ -308,7 +309,7 @@ namespace osu.Game.Beatmaps.Formats bool omitFirstBarSignature = false; if (split.Length >= 8) { - EffectFlags effectFlags = (EffectFlags)int.Parse(split[7]); + EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); } From 04b88cdd11889d309777a93937066ab943d968c6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 13 Mar 2019 14:12:05 +0900 Subject: [PATCH 42/59] Add required types for BeatmapDetailArea test case --- osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index ff39ad5c0d..6cc3982f9c 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -14,6 +16,8 @@ namespace osu.Game.Tests.Visual [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] public class TestCaseBeatmapDetailArea : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) }; + public TestCaseBeatmapDetailArea() { BeatmapDetailArea detailsArea; From e6449db8e374f33779e1d9326e43c5f6270c7603 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 13 Mar 2019 14:12:36 +0900 Subject: [PATCH 43/59] Null metrics on null beatmap for transition animation --- osu.Game/Screens/Select/BeatmapDetails.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 6057ba382b..46979d0000 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -182,6 +182,7 @@ namespace osu.Game.Screens.Select if (Beatmap == null) { + updateMetrics(null); ratingsContainer.FadeOut(transition_duration); failRetryContainer.FadeOut(transition_duration); return; From 241e336c212ae097511b4bb93f2c62cd82fb691a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 14:22:16 +0900 Subject: [PATCH 44/59] Fix break parsing --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9b3607029f..d51bb8d6ca 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -264,7 +264,7 @@ namespace osu.Game.Beatmaps.Formats var breakEvent = new BreakPeriod { StartTime = start, - EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[1]))) + EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))) }; if (!breakEvent.HasEffect) From 8f00f2290a1ffb9591521affbc076dc9d958d15f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 14:56:41 +0900 Subject: [PATCH 45/59] Log issues --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 +++ osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index d51bb8d6ca..a27126ad9c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; using osu.Framework.IO.File; +using osu.Framework.Logging; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -351,9 +352,11 @@ namespace osu.Game.Beatmaps.Formats } catch (FormatException) { + Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } catch (OverflowException) { + Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 73b3e823fd..8d6bb8bd3f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -225,9 +225,11 @@ namespace osu.Game.Rulesets.Objects.Legacy } catch (FormatException) { + Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } catch (OverflowException) { + Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } return null; From 3d4a80957fc52198c1225ad64acb8b5ba1fd6d00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 18:27:54 +0900 Subject: [PATCH 46/59] Improve comment --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index b273faec8f..89db954c36 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Judgements protected SpriteText JudgementText; /// - /// The amount of time for this judgement to initially fade in. + /// Duration of initial fade in. + /// Default fade out will start immediately after this duration. /// - /// Override this to change fade in times of mode-specific judgements protected virtual double FadeInDuration => 100; /// From dd6fbccb5614a62ae943ab0ef53584729221038b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 19:57:45 +0900 Subject: [PATCH 47/59] Slight refactoring of order for readability --- osu.Game/Screens/Select/BeatmapDetails.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 46979d0000..2784e4fc3b 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; +using JetBrains.Annotations; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Threading; @@ -180,15 +181,15 @@ namespace osu.Game.Screens.Select source.Text = Beatmap?.Metadata?.Source; tags.Text = Beatmap?.Metadata?.Tags; - if (Beatmap == null) + // metrics may have been previously fetched + if (Beatmap?.Metrics != null) { - updateMetrics(null); - ratingsContainer.FadeOut(transition_duration); - failRetryContainer.FadeOut(transition_duration); + updateMetrics(Beatmap.Metrics); return; } - if (Beatmap.Metrics == null && Beatmap.OnlineBeatmapID != null) + // metrics may not be fetched but can be + if (Beatmap?.OnlineBeatmapID != null) { var requestedBeatmap = Beatmap; var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); @@ -204,14 +205,13 @@ namespace osu.Game.Screens.Select lookup.Failure += e => Schedule(() => updateMetrics(null)); api.Queue(lookup); loading.Show(); + return; } - else - { - updateMetrics(Beatmap.Metrics); - } + + updateMetrics(null); } - private void updateMetrics(BeatmapMetrics metrics) + private void updateMetrics([CanBeNull] BeatmapMetrics metrics) { var hasRatings = metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); From 5d23a966d71984f170fc20fad3932829020e4e52 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 13 Mar 2019 20:55:33 +0900 Subject: [PATCH 48/59] Fix incorrect application logic for rate fallback Co-Authored-By: peppy --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 1573418d95..0fadbd1d39 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mods if (clock is IHasPitchAdjust tempo) tempo.PitchAdjust = baseAdjust * localAdjust; else - clock.Rate *= baseAdjust * localAdjust; + clock.Rate = baseAdjust * localAdjust; } } } From c40e24739b2040a62035021786226e26b9cdfaf0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 21:37:33 +0900 Subject: [PATCH 49/59] Fix weird application logic --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 0fadbd1d39..082f0091c6 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -62,12 +62,14 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { var absRate = Math.Abs(FinalRateAdjustment); - applyAdjustment(MathHelper.Clamp(absRate * ((clock.CurrentTime - beginRampTime) / finalRateTime), 0, absRate)); + applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime); } - private void applyAdjustment(double adjustment) + private void applyAdjustment(double adjustAmount) { - var localAdjust = 1 + Math.Sign(FinalRateAdjustment) * adjustment; + adjustAmount = MathHelper.Clamp(adjustAmount, 0, 1); + + var localAdjust = 1 + Math.Sign(FinalRateAdjustment) * adjustAmount * Math.Abs(FinalRateAdjustment); if (clock is IHasPitchAdjust tempo) tempo.PitchAdjust = baseAdjust * localAdjust; From 108dcd0a92e61a281e4bd7308e7b4c3e55e0c66f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 21:58:50 +0900 Subject: [PATCH 50/59] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2e945c212d..e2c96effb6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b25e2a8bb2..288630d1ec 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 85c518f146f4b8ff1c8bbd7971d0023545825701 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Mar 2019 12:29:16 +0900 Subject: [PATCH 51/59] Change logic to better handle external adjustments --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 43 +++++++++++++++++---------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 082f0091c6..8cd4b28ce3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -34,18 +34,11 @@ namespace osu.Game.Rulesets.Mods /// private const double final_rate_progress = 0.75f; - /// - /// The adjustment applied on entering this mod's application method. - /// - private double baseAdjust; - public virtual void ApplyToClock(IAdjustableClock clock) { this.clock = clock; - // we capture the adjustment applied before entering our application method. - // this will cover external changes, which should re-fire this method. - baseAdjust = (clock as IHasPitchAdjust)?.PitchAdjust ?? clock.Rate; + lastAdjust = 1; // for preview purposes. during gameplay, Update will overwrite this setting. applyAdjustment(1); @@ -61,20 +54,38 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { - var absRate = Math.Abs(FinalRateAdjustment); applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime); } - private void applyAdjustment(double adjustAmount) + private double lastAdjust = 1; + + private bool reported; + + /// + /// Adjust the rate along the specified ramp + /// + /// The amount of adjustment to apply (from 0..1). + private void applyAdjustment(double amount) { - adjustAmount = MathHelper.Clamp(adjustAmount, 0, 1); + double adjust = 1 + (Math.Sign(FinalRateAdjustment) * MathHelper.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); - var localAdjust = 1 + Math.Sign(FinalRateAdjustment) * adjustAmount * Math.Abs(FinalRateAdjustment); + switch (clock) + { + case IHasPitchAdjust pitch: + pitch.PitchAdjust /= lastAdjust; + pitch.PitchAdjust *= adjust; + break; + case IHasTempoAdjust tempo: + tempo.TempoAdjust /= lastAdjust; + tempo.TempoAdjust *= adjust; + break; + default: + clock.Rate /= lastAdjust; + clock.Rate *= adjust; + break; + } - if (clock is IHasPitchAdjust tempo) - tempo.PitchAdjust = baseAdjust * localAdjust; - else - clock.Rate = baseAdjust * localAdjust; + lastAdjust = adjust; } } } From 7d56ce63d38433541f12932b2b015d9f6f059615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Mar 2019 12:31:17 +0900 Subject: [PATCH 52/59] Fix test case failures --- osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs index 14b4af1e7d..3b3adb4d76 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestCase.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - Beatmap.Value.Track.Rate = Clock.Rate; + Beatmap.Value.Track.TempoAdjust = Clock.Rate; } } } From 69eb4ef9830f41c425c08ac307f5083cd78934b5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 14 Mar 2019 13:23:31 +0900 Subject: [PATCH 53/59] Change updateMetrics parameter to be optional --- osu.Game/Screens/Select/BeatmapDetails.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 2784e4fc3b..e8ef94c101 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; -using JetBrains.Annotations; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Threading; @@ -202,16 +201,16 @@ namespace osu.Game.Screens.Select requestedBeatmap.Metrics = res; Schedule(() => updateMetrics(res)); }; - lookup.Failure += e => Schedule(() => updateMetrics(null)); + lookup.Failure += e => Schedule(() => updateMetrics()); api.Queue(lookup); loading.Show(); return; } - updateMetrics(null); + updateMetrics(); } - private void updateMetrics([CanBeNull] BeatmapMetrics metrics) + private void updateMetrics(BeatmapMetrics metrics = null) { var hasRatings = metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); From 2815c3b07cc296b8c13c9d8c0006f4e1ae75aee7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Mar 2019 15:12:26 +0900 Subject: [PATCH 54/59] Remove unused variable --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 8cd4b28ce3..62407907c1 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -59,8 +59,6 @@ namespace osu.Game.Rulesets.Mods private double lastAdjust = 1; - private bool reported; - /// /// Adjust the rate along the specified ramp /// From 71d79f0a3914a2f858514e9b173e4f4db93a36aa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Mar 2019 15:20:09 +0900 Subject: [PATCH 55/59] Fix ticks not being generated at the minimum tick distance --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index a0f9d0a481..7cda7485f9 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects for (var d = tickDistance; d <= length; d += tickDistance) { - if (d > length - minDistanceFromEnd) + if (d >= length - minDistanceFromEnd) break; var pathProgress = d / length; From 5cc670cd19012a0a6b26d83404e7221c5faf9826 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 14 Mar 2019 18:51:28 +0900 Subject: [PATCH 56/59] Prevent ApplyTransformsAt from propogating to SliderBall children --- .../Objects/Drawables/Pieces/SliderBall.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 1b2e2c1f47..e41c568403 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -132,6 +132,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces base.ClearTransformsAfter(time, false, targetMember); } + public override void ApplyTransformsAt(double time, bool propagateChildren = false) + { + // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either. + base.ApplyTransformsAt(time, false); + } + private bool tracking; public bool Tracking From 144b8761d4e01fcb9c6aa1bf7f5a9e10c2b8275e Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 14 Mar 2019 19:15:47 +0900 Subject: [PATCH 57/59] Change "Redundant argument with default value" inspection from warning to hint. --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index b1ac31f2ce..71cbd83e3e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -111,6 +111,7 @@ HINT WARNING WARNING + HINT WARNING WARNING WARNING From d13423fcdbcf7a723c0c7afaa0ea66f4c9b5bb00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Mar 2019 23:06:23 +0900 Subject: [PATCH 58/59] Fix catch difficulty calculator not providing adjustment mods --- .../Difficulty/CatchDifficultyCalculator.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 8cfda5d532..f3b88bd928 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Difficulty.Skills; +using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty; @@ -88,5 +89,13 @@ namespace osu.Game.Rulesets.Catch.Difficulty { new Movement(), }; + + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new CatchModDoubleTime(), + new CatchModHalfTime(), + new CatchModHardRock(), + new CatchModEasy(), + }; } } From e31680e3730a04a27dacd019476a41b002e12bac Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 14 Mar 2019 16:05:54 -0700 Subject: [PATCH 59/59] Address styling issues on CodeFactor --- osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs | 1 - osu.iOS/Application.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs index c38cd19b42..026442f328 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -13,7 +13,6 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { - } } } diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index 8a5cfcdbe8..cb75e5c159 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -13,4 +13,3 @@ namespace osu.iOS } } } -