From 902be0d059ca8fb4033e674bacafebe5dea2b7fb Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Thu, 31 Jan 2019 17:03:43 +0800 Subject: [PATCH 01/76] add grow mod --- osu-resources | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 53 ++++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs diff --git a/osu-resources b/osu-resources index 677897728f..9880089b4e 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 677897728f4332fa1200e0280ca02c4b987c6c47 +Subproject commit 9880089b4e8fcd78d68f30c8a40d43bf8dccca86 diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs new file mode 100644 index 0000000000..5ab1ea698b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Grow"; + public override string Acronym => "GR"; + public override FontAwesome Icon => FontAwesome.fa_arrows_v; + public override ModType Type => ModType.Fun; + public override string Description => "Hit them at the right size!"; + public override double ScoreMultiplier => 1; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + { + if (drawable is DrawableSpinner spinner) + return; + + drawable.ApplyCustomUpdateState += applyCustomState; + } + } + + protected virtual void applyCustomState(DrawableHitObject drawable, ArmedState state) + { + var hitObject = (OsuHitObject) drawable.HitObject; + + double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; + double scaleDuration = hitObject.TimePreempt + 1; + + var originalScale = drawable.Scale; + drawable.Scale /= 2; + + using (drawable.BeginAbsoluteSequence(appearTime, true)) + drawable.ScaleTo(originalScale, scaleDuration, Easing.OutSine); + + if (drawable is DrawableHitCircle circle) + using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) + circle.ApproachCircle.Hide(); + } + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 12d0a28a8f..200f4af3da 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.Osu return new Mod[] { new OsuModTransform(), new OsuModWiggle(), + new OsuModGrow() }; default: return new Mod[] { }; From a8d30f6aee41f38fcbdd35df05e3a010675e9f5b Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Thu, 31 Jan 2019 17:05:50 +0800 Subject: [PATCH 02/76] remove unused using --- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 5ab1ea698b..b64cfa3ef2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK; namespace osu.Game.Rulesets.Osu.Mods { From 7e2f4af00dd2d47212f1208857bccc936b057e0d Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Thu, 31 Jan 2019 17:58:09 +0800 Subject: [PATCH 03/76] remove whitespaces --- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index b64cfa3ef2..8235a2d6a9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -26,13 +26,12 @@ namespace osu.Game.Rulesets.Osu.Mods { if (drawable is DrawableSpinner spinner) return; - drawable.ApplyCustomUpdateState += applyCustomState; } } protected virtual void applyCustomState(DrawableHitObject drawable, ArmedState state) - { + { var hitObject = (OsuHitObject) drawable.HitObject; double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; From f50a0be29d63f5c5b049ab54c9583d4d1ed00ecc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Feb 2019 16:22:14 +0900 Subject: [PATCH 04/76] Add osu! difficulty calculator test --- .../OsuDifficultyCalculatorTest.cs | 32 ++++ .../Testing/Beatmaps/diffcalc-test.osu | 167 ++++++++++++++++++ .../Beatmaps/DifficultyCalculatorTest.cs | 44 +++++ 3 files changed, 243 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu create mode 100644 osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..b8b5e3bb36 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; + + [Test] + public new void Test() + { + base.Test(6.9311449688341344, "diffcalc-test"); + } + + private void openUsingShellExecute(string path) => Process.Start(new ProcessStartInfo + { + FileName = path, + UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361 + }); + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); + } +} diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..4b42cd4ffd --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,167 @@ +osu file format v14 + +[General] +StackLeniency: 0.3 +Mode: 0 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +62500,-500,4,2,1,50,0,0 +71000,-100,4,2,1,50,0,0 + +[HitObjects] +// Circles spaced 1 beat apart, with increasing jump distance +126,112,500,5,0,0:0:0:0: +130,155,1000,1,0,0:0:0:0: +131,269,1500,1,0,0:0:0:0: +341,269,2000,1,0,0:0:0:0: +113,95,2500,1,0,0:0:0:0: + +// Circles spaced 1/2 beat apart, with increasing jump distance +108,104,3500,5,0,0:0:0:0: +110,145,3750,1,0,0:0:0:0: +115,262,4000,1,0,0:0:0:0: +285,265,4250,1,0,0:0:0:0: +458,48,4500,1,0,0:0:0:0: +35,199,4750,1,0,0:0:0:0: +251,340,5000,1,0,0:0:0:0: +20,352,5250,1,0,0:0:0:0: +426,62,5500,1,0,0:0:0:0: + +// Circles spaced 1/4 beat apart, with increasing jump distances +211,138,6500,5,0,0:0:0:0: +99,256,6625,1,0,0:0:0:0: +68,129,6750,1,0,0:0:0:0: +371,340,6875,1,0,0:0:0:0: +241,219,7000,1,0,0:0:0:0: +252,148,7125,1,0,0:0:0:0: +434,97,7250,1,0,0:0:0:0: +40,38,7375,1,0,0:0:0:0: +114,334,7500,1,0,0:0:0:0: +301,19,7625,1,0,0:0:0:0: +441,241,7750,1,0,0:0:0:0: +121,91,7875,1,0,0:0:0:0: +270,384,8000,1,0,0:0:0:0: +488,92,8125,1,0,0:0:0:0: +332,82,8250,1,0,0:0:0:0: +108,240,8375,1,0,0:0:0:0: +281,268,8500,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/2 beat apart, small jump distances, changing angles +252,191,9500,5,0,0:0:0:0: +356,191,9750,1,0,0:0:0:0: +311,268,10000,1,0,0:0:0:0: +190,270,10250,1,0,0:0:0:0: +107,199,10500,1,0,0:0:0:0: +172,105,10750,1,0,0:0:0:0: +297,102,11000,1,0,0:0:0:0: +373,178,11250,1,0,0:0:0:0: +252,195,11500,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/2 beat apart, large jump distances, changing angles +140,187,12500,5,0,0:0:0:0: +451,331,12750,1,0,0:0:0:0: +46,338,13000,1,0,0:0:0:0: +204,50,13250,1,0,0:0:0:0: +464,162,13500,1,0,0:0:0:0: +252,346,13750,1,0,0:0:0:0: +13,175,14000,1,0,0:0:0:0: +488,181,14250,1,0,0:0:0:0: +251,187,14500,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/4 beat apart, small jump distances, changing angles +188,192,15500,5,0,0:0:0:0: +298,194,15625,1,0,0:0:0:0: +317,84,15750,1,0,0:0:0:0: +185,85,15875,1,0,0:0:0:0: +77,200,16000,1,0,0:0:0:0: +184,303,16125,1,0,0:0:0:0: +295,225,16250,1,0,0:0:0:0: +300,84,16375,1,0,0:0:0:0: +144,82,16500,1,0,0:0:0:0: +141,215,16625,1,0,0:0:0:0: +314,184,16750,1,0,0:0:0:0: +188,192,16875,1,0,0:0:0:0: +188,192,17000,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/4 beat apart, large jump distances, changing angles +97,192,18000,5,0,0:0:0:0: +336,38,18125,1,0,0:0:0:0: +440,322,18250,1,0,0:0:0:0: +39,331,18375,1,0,0:0:0:0: +98,39,18500,1,0,0:0:0:0: +460,179,18625,1,0,0:0:0:0: +245,338,18750,1,0,0:0:0:0: +12,184,18875,1,0,0:0:0:0: +250,41,19000,1,0,0:0:0:0: +265,193,19125,1,0,0:0:0:0: +486,22,19250,1,0,0:0:0:0: +411,205,19375,1,0,0:0:0:0: +107,198,19500,1,0,0:0:0:0: + +// Short sliders spaced 1 beat apart +28,108,20500,2,0,L|196:107,1,160 +25,177,21500,2,0,L|193:176,1,160 +26,308,22500,2,0,L|194:307,1,160 +320,89,23500,2,0,L|488:88,1,160 + +// Short sliders spaced 1/2 beat apart +28,108,25000,6,0,L|196:107,1,160 +27,173,25750,2,0,L|195:172,1,160 +25,292,26500,2,0,L|193:291,1,160 +340,213,27250,2,0,L|508:212,1,160 +21,44,28000,2,0,L|189:43,1,160 + +// Short sliders spaced 1/4 beat apart +28,108,29500,6,0,L|196:107,1,160 +30,169,30125,2,0,L|198:168,1,160 +35,282,30750,2,0,L|203:281,1,160 +327,286,31375,2,0,L|495:285,1,160 +51,61,32000,2,0,L|219:60,1,160 + +// Large, medium-paced slider shapes +// PerfectCurve +66,86,33500,6,0,P|246:348|427:44,1,800 +66,86,36500,2,0,P|246:348|427:44,1,800 +66,86,39500,2,0,P|246:348|427:44,1,800 +// Linear +66,72,42500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120 +66,72,46500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120 +66,72,50500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120 +// Bezier +76,287,54500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640 +76,287,57000,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640 +76,287,59500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640 + +// Large slow slider with many ticks +81,170,62500,6,0,P|263:78|168:268,1,480 + +// Fast slider with many repeats +102,152,71000,6,0,L|175:153,18,64 + +// Slider-circle combos, spaced 1/2 beat apart +106,204,75500,6,0,P|275:33|171:304,1,800 +255,179,78250,1,0,0:0:0:0: +106,204,78500,2,0,P|275:33|171:304,1,800 +255,179,81250,1,0,0:0:0:0: +106,204,81500,2,0,P|275:33|171:304,1,800 + +// Circle-spinner combos, spaced 1/2 beat apart +82,69,85000,5,0,0:0:0:0: +256,192,85250,8,0,86000,0:0:0:0: +83,69,86250,5,0,0:0:0:0: +256,192,86500,12,0,87000,0:0:0:0: + +// Spinner-spinner combos, spaced 1/2 beat apart +256,192,88000,12,0,89000,0:0:0:0: +256,192,89250,12,0,90250,0:0:0:0: +256,192,90500,12,0,91500,0:0:0:0: +256,192,91750,12,0,92750,0:0:0:0: +256,192,93000,12,0,94000,0:0:0:0: diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs new file mode 100644 index 0000000000..c6a7ff7cf4 --- /dev/null +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -0,0 +1,44 @@ +// 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.IO; +using System.Reflection; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public abstract class DifficultyCalculatorTest + { + private const string resource_namespace = "Testing.Beatmaps"; + + protected abstract string ResourceAssembly { get; } + + protected void Test(double expected, string name, params Mod[] mods) + => Assert.AreEqual(expected, CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating); + + private WorkingBeatmap getBeatmap(string name) + { + using (var resStream = openResource($"{resource_namespace}.{name}.osu")) + using (var stream = new StreamReader(resStream)) + { + var decoder = Decoder.GetDecoder(stream); + ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; + return new TestWorkingBeatmap(decoder.Decode(stream)); + } + } + + private Stream openResource(string name) + { + var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); + return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); + } + + protected abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); + } +} From 1b61ec4ef402aa863671c552fc7f12aad2280517 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Feb 2019 18:05:23 +0900 Subject: [PATCH 05/76] First pass clean-up --- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 46 +++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 8235a2d6a9..c7e43dc006 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -14,38 +14,50 @@ namespace osu.Game.Rulesets.Osu.Mods internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects { public override string Name => "Grow"; + public override string Acronym => "GR"; + public override FontAwesome Icon => FontAwesome.fa_arrows_v; + public override ModType Type => ModType.Fun; + public override string Description => "Hit them at the right size!"; + public override double ScoreMultiplier => 1; public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var drawable in drawables) { - if (drawable is DrawableSpinner spinner) - return; - drawable.ApplyCustomUpdateState += applyCustomState; + switch (drawable) + { + case DrawableSpinner _: + continue; + default: + drawable.ApplyCustomUpdateState += ApplyCustomState; + break; + } } } - protected virtual void applyCustomState(DrawableHitObject drawable, ArmedState state) + protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state) { - var hitObject = (OsuHitObject) drawable.HitObject; + var h = (OsuHitObject)drawable.HitObject; - double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; - double scaleDuration = hitObject.TimePreempt + 1; + var scale = drawable.Scale; + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + drawable.ScaleTo(scale / 2).Then().ScaleTo(scale, h.TimePreempt, Easing.OutSine); - var originalScale = drawable.Scale; - drawable.Scale /= 2; - - using (drawable.BeginAbsoluteSequence(appearTime, true)) - drawable.ScaleTo(originalScale, scaleDuration, Easing.OutSine); - - if (drawable is DrawableHitCircle circle) - using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) - circle.ApproachCircle.Hide(); + switch (drawable) + { + case DrawableHitCircle circle: + { + // we don't want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + circle.ApproachCircle.Hide(); + break; + } + } } } -} \ No newline at end of file +} From 810175235d17adca7f44647dfa496b1993ff48bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Feb 2019 18:47:05 +0900 Subject: [PATCH 06/76] Fix incorrect application of scaling in some cases Isolates different usages of hitcircle scale so they can't ever cause regressions. --- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 21 ++++-- .../Objects/Drawables/DrawableHitCircle.cs | 67 ++++++++++++------- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index c7e43dc006..65e9eb7a1d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -44,19 +44,30 @@ namespace osu.Game.Rulesets.Osu.Mods { var h = (OsuHitObject)drawable.HitObject; - var scale = drawable.Scale; - using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - drawable.ScaleTo(scale / 2).Then().ScaleTo(scale, h.TimePreempt, Easing.OutSine); + // apply grow effect + switch (drawable) + { + case DrawableSliderHead _: + case DrawableSliderTail _: + // special cases we should *not* be scaling. + break; + case DrawableSlider _: + case DrawableHitCircle _: + { + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + drawable.ScaleTo(0.5f).Then().ScaleTo(1, h.TimePreempt, Easing.OutSine); + break; + } + } + // remove approach circles switch (drawable) { case DrawableHitCircle circle: - { // we don't want to see the approach circle using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) circle.ApproachCircle.Hide(); break; - } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index df0769982d..7dd2fa69ce 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osuTK; @@ -27,40 +28,58 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); + private readonly Container explodeContainer; + + private readonly Container scaleContainer; + public DrawableHitCircle(HitCircle h) : base(h) { Origin = Anchor.Centre; Position = HitObject.StackedPosition; - Scale = new Vector2(h.Scale); InternalChildren = new Drawable[] { - glow = new GlowPiece(), - circle = new CirclePiece + scaleContainer = new Container { - Hit = () => + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = explodeContainer = new Container { - if (AllJudged) - return false; + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + glow = new GlowPiece(), + circle = new CirclePiece + { + Hit = () => + { + if (AllJudged) + return false; - UpdateResult(true); - return true; - }, + UpdateResult(true); + return true; + }, + }, + number = new NumberPiece + { + Text = (HitObject.IndexInCurrentCombo + 1).ToString(), + }, + ring = new RingPiece(), + flash = new FlashPiece(), + explode = new ExplodePiece(), + ApproachCircle = new ApproachCircle + { + Alpha = 0, + Scale = new Vector2(4), + } + } + } }, - number = new NumberPiece - { - Text = (HitObject.IndexInCurrentCombo + 1).ToString(), - }, - ring = new RingPiece(), - flash = new FlashPiece(), - explode = new ExplodePiece(), - ApproachCircle = new ApproachCircle - { - Alpha = 0, - Scale = new Vector2(4), - } }; //may not be so correct @@ -72,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - scaleBindable.BindValueChanged(v => Scale = new Vector2(v)); + scaleBindable.BindValueChanged(v => scaleContainer.Scale = new Vector2(v), true); positionBindable.BindTo(HitObject.PositionBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable); @@ -156,8 +175,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.FadeOut(); number.FadeOut(); - this.FadeOut(800) - .ScaleTo(Scale * 1.5f, 400, Easing.OutQuad); + this.FadeOut(800); + explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad); } Expire(); From 8becd7ff92dbc82b451100ea7862e58bcb7badb4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Feb 2019 12:49:48 +0900 Subject: [PATCH 07/76] Add a slider-spinner test case --- .../OsuDifficultyCalculatorTest.cs | 2 +- .../Resources/Testing/Beatmaps/diffcalc-test.osu | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index b8b5e3bb36..5f290886c2 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public new void Test() { - base.Test(6.9311449688341344, "diffcalc-test"); + base.Test(6.931145117263422d, "diffcalc-test"); } private void openUsingShellExecute(string path) => Process.Start(new ProcessStartInfo diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu index 4b42cd4ffd..bf345811a2 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -156,7 +156,7 @@ SliderTickRate:1 // Circle-spinner combos, spaced 1/2 beat apart 82,69,85000,5,0,0:0:0:0: 256,192,85250,8,0,86000,0:0:0:0: -83,69,86250,5,0,0:0:0:0: +384,189,86250,5,0,0:0:0:0: 256,192,86500,12,0,87000,0:0:0:0: // Spinner-spinner combos, spaced 1/2 beat apart @@ -165,3 +165,15 @@ SliderTickRate:1 256,192,90500,12,0,91500,0:0:0:0: 256,192,91750,12,0,92750,0:0:0:0: 256,192,93000,12,0,94000,0:0:0:0: + +// Slider-spinner combos, spaced 1/2 beat apart +49,89,95000,6,0,L|214:87,1,160 +256,192,95625,12,0,96500,0:0:0:0: +12,299,96625,6,0,L|177:297,1,160 +256,192,97250,12,0,98125,0:0:0:0: +295,107,98250,6,0,L|460:105,1,160 +256,192,98875,12,0,99750,0:0:0:0: +279,325,99875,6,0,L|444:323,1,160 +256,192,100500,12,0,101375,0:0:0:0: +197,197,101500,6,0,L|362:195,1,160 +256,192,102125,12,0,103000,0:0:0:0: From 490d48fa5e106b88b5aac1952812689ec444c889 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Feb 2019 13:47:26 +0900 Subject: [PATCH 08/76] Fix MultiplayerTestCase not being abstract --- osu.Game/Tests/Visual/MultiplayerTestCase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/MultiplayerTestCase.cs b/osu.Game/Tests/Visual/MultiplayerTestCase.cs index 6efdddbfee..7e2f915179 100644 --- a/osu.Game/Tests/Visual/MultiplayerTestCase.cs +++ b/osu.Game/Tests/Visual/MultiplayerTestCase.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Multiplayer; namespace osu.Game.Tests.Visual { - public class MultiplayerTestCase : OsuTestCase + public abstract class MultiplayerTestCase : OsuTestCase { [Cached] private readonly Bindable currentRoom = new Bindable(new Room()); From 280081d58938cd3294a59d63fc6645aed26eaad2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Feb 2019 14:42:42 +0900 Subject: [PATCH 09/76] Fix beatmap ruleset not being set --- .../OsuDifficultyCalculatorTest.cs | 2 ++ osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 5f290886c2..7f6591ffcf 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -28,5 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index c6a7ff7cf4..108fa8ff71 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -7,6 +7,7 @@ using System.Reflection; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; +using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -29,7 +30,11 @@ namespace osu.Game.Tests.Beatmaps { var decoder = Decoder.GetDecoder(stream); ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; - return new TestWorkingBeatmap(decoder.Decode(stream)); + + var working = new TestWorkingBeatmap(decoder.Decode(stream)); + working.BeatmapInfo.Ruleset = CreateRuleset().RulesetInfo; + + return working; } } @@ -40,5 +45,7 @@ namespace osu.Game.Tests.Beatmaps } protected abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); + + protected abstract Ruleset CreateRuleset(); } } From c3138db390580eec48530eaf67aa1fa9e16c80a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Feb 2019 14:42:52 +0900 Subject: [PATCH 10/76] Cleanup osu difficulty test --- .../OsuDifficultyCalculatorTest.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 7f6591ffcf..4926a81034 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.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.Diagnostics; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; @@ -15,18 +14,12 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [Test] - public new void Test() + [TestCase(6.931145117263422, "diffcalc-test")] + public void Test(double expected, string name) { - base.Test(6.931145117263422d, "diffcalc-test"); + base.Test(expected, name); } - private void openUsingShellExecute(string path) => Process.Start(new ProcessStartInfo - { - FileName = path, - UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361 - }); - protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); protected override Ruleset CreateRuleset() => new OsuRuleset(); From aa0bb7ca1109e965ddd19f0bb3d59221a70d386f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Feb 2019 14:44:26 +0900 Subject: [PATCH 11/76] Add taiko difficulty calculator tests --- .../TaikoDifficultyCalculatorTest.cs | 27 ++ .../Testing/Beatmaps/diffcalc-test-strong.osu | 257 ++++++++++++++++ .../Testing/Beatmaps/diffcalc-test.osu | 285 ++++++++++++++++++ 3 files changed, 569 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..112834e84c --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -0,0 +1,27 @@ +// 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; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TaikoDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; + + [TestCase(2.9811336589467095, "diffcalc-test")] + [TestCase(2.9811336589467095, "diffcalc-test-strong")] + public void Test(double expected, string name) + { + base.Test(expected, name); + } + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu new file mode 100644 index 0000000000..33510eceb7 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu @@ -0,0 +1,257 @@ +osu file format v14 + +[General] +Mode: 1 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +62500,-500,4,2,1,50,0,0 +71000,-100,4,2,1,50,0,0 + +[HitObjects] +// Same as diffcalc-test with finishers on every note +142,122,0,5,4,0:0:0:0: +142,122,125,1,4,0:0:0:0: +142,122,250,1,4,0:0:0:0: +142,122,375,1,4,0:0:0:0: +142,122,500,1,4,0:0:0:0: +142,122,625,1,4,0:0:0:0: +142,122,750,1,4,0:0:0:0: +142,122,875,1,4,0:0:0:0: +142,122,1000,1,4,0:0:0:0: +142,122,1125,1,4,0:0:0:0: +142,122,1250,1,4,0:0:0:0: +142,122,1375,1,4,0:0:0:0: +142,122,1500,1,4,0:0:0:0: +119,106,2500,1,6,0:0:0:0: +119,106,2625,1,6,0:0:0:0: +119,106,2750,1,6,0:0:0:0: +119,106,2875,1,6,0:0:0:0: +119,106,3000,1,6,0:0:0:0: +119,106,3125,1,6,0:0:0:0: +119,106,3250,1,6,0:0:0:0: +119,106,3375,1,6,0:0:0:0: +119,106,3500,1,6,0:0:0:0: +119,106,3625,1,6,0:0:0:0: +119,106,3750,1,6,0:0:0:0: +119,106,3875,1,6,0:0:0:0: +119,106,4000,1,6,0:0:0:0: +136,90,5000,1,4,0:0:0:0: +136,90,5125,1,6,0:0:0:0: +136,90,5250,1,4,0:0:0:0: +136,90,5375,1,6,0:0:0:0: +136,90,5500,1,4,0:0:0:0: +136,90,5625,1,6,0:0:0:0: +136,90,5750,1,4,0:0:0:0: +136,90,5875,1,6,0:0:0:0: +136,90,6000,1,4,0:0:0:0: +136,90,6125,1,6,0:0:0:0: +136,90,6250,1,4,0:0:0:0: +136,90,6375,1,6,0:0:0:0: +136,90,6500,1,4,0:0:0:0: +86,113,7500,1,4,0:0:0:0: +86,113,7625,1,4,0:0:0:0: +86,113,7750,1,6,0:0:0:0: +86,113,7875,1,6,0:0:0:0: +86,113,8000,1,4,0:0:0:0: +86,113,8125,1,4,0:0:0:0: +86,113,8250,1,6,0:0:0:0: +86,113,8375,1,6,0:0:0:0: +86,113,8500,1,4,0:0:0:0: +86,113,8625,1,4,0:0:0:0: +86,113,8750,1,6,0:0:0:0: +86,113,8875,1,6,0:0:0:0: +86,113,9000,1,4,0:0:0:0: +146,90,10000,1,4,0:0:0:0: +146,90,10125,1,4,0:0:0:0: +146,90,10250,1,4,0:0:0:0: +146,90,10375,1,6,0:0:0:0: +146,90,10500,1,6,0:0:0:0: +146,90,10625,1,6,0:0:0:0: +146,90,10750,1,4,0:0:0:0: +146,90,10875,1,4,0:0:0:0: +146,90,11000,1,4,0:0:0:0: +146,90,11125,1,6,0:0:0:0: +146,90,11250,1,6,0:0:0:0: +146,90,11375,1,6,0:0:0:0: +146,90,11500,1,4,0:0:0:0: +146,90,11625,1,4,0:0:0:0: +146,90,11750,1,4,0:0:0:0: +146,90,11875,1,6,0:0:0:0: +146,90,12000,1,6,0:0:0:0: +146,90,12125,1,6,0:0:0:0: +146,90,12250,1,4,0:0:0:0: +146,90,12375,1,4,0:0:0:0: +146,90,12500,1,4,0:0:0:0: +69,99,13500,1,4,0:0:0:0: +69,99,13625,1,4,0:0:0:0: +69,99,13750,1,4,0:0:0:0: +69,99,13875,1,6,0:0:0:0: +69,99,14000,1,4,0:0:0:0: +69,99,14125,1,4,0:0:0:0: +69,99,14250,1,4,0:0:0:0: +69,99,14375,1,6,0:0:0:0: +69,99,14500,1,4,0:0:0:0: +69,99,14625,1,4,0:0:0:0: +69,99,14750,1,4,0:0:0:0: +69,99,14875,1,6,0:0:0:0: +69,99,15000,1,4,0:0:0:0: +69,99,15125,1,4,0:0:0:0: +69,99,15250,1,4,0:0:0:0: +69,99,15375,1,6,0:0:0:0: +69,99,15500,1,4,0:0:0:0: +83,89,16500,1,4,0:0:0:0: +83,89,16625,1,6,0:0:0:0: +83,89,16750,1,6,0:0:0:0: +83,89,16875,1,4,0:0:0:0: +83,89,17000,1,4,0:0:0:0: +83,89,17125,1,4,0:0:0:0: +83,89,17250,1,6,0:0:0:0: +83,89,17375,1,6,0:0:0:0: +83,89,17500,1,6,0:0:0:0: +83,89,17625,1,6,0:0:0:0: +83,89,17750,1,4,0:0:0:0: +83,89,17875,1,4,0:0:0:0: +83,89,18000,1,4,0:0:0:0: +83,89,18125,1,4,0:0:0:0: +83,89,18250,1,4,0:0:0:0: +83,89,18375,1,6,0:0:0:0: +83,89,18500,1,6,0:0:0:0: +83,89,18625,1,6,0:0:0:0: +83,89,18750,1,6,0:0:0:0: +83,89,18875,1,4,0:0:0:0: +83,89,19000,1,4,0:0:0:0: +83,89,19125,1,4,0:0:0:0: +83,89,19250,1,4,0:0:0:0: +83,89,19375,1,6,0:0:0:0: +83,89,19500,1,6,0:0:0:0: +83,89,19625,1,4,0:0:0:0: +84,122,20500,1,4,0:0:0:0: +84,122,20625,2,4,L|217:123,1,120 +84,122,21125,1,4,0:0:0:0: +84,122,21250,2,4,L|217:123,1,120 +84,122,21750,1,4,0:0:0:0: +84,122,21875,2,4,L|217:123,1,120 +84,122,22375,1,4,0:0:0:0: +84,122,22500,2,4,L|217:123,1,120 +84,122,23000,1,4,0:0:0:0: +84,122,23125,2,4,L|217:123,1,120 +99,106,24500,1,4,0:0:0:0: +99,106,24625,1,4,0:0:0:0: +99,106,24750,2,4,L|194:107,1,80 +99,106,25125,1,4,0:0:0:0: +99,106,25250,1,4,0:0:0:0: +99,106,25375,2,4,L|194:107,1,80 +99,106,25750,1,4,0:0:0:0: +99,106,25875,1,4,0:0:0:0: +99,106,26000,2,4,L|194:107,1,80 +99,106,26375,1,4,0:0:0:0: +99,106,26500,1,4,0:0:0:0: +99,106,26625,2,4,L|194:107,1,80 +99,106,27000,1,4,0:0:0:0: +99,106,27125,1,4,0:0:0:0: +99,106,27250,2,4,L|194:107,1,80 +121,103,28500,1,4,0:0:0:0: +121,103,28625,1,4,0:0:0:0: +121,103,28750,1,4,0:0:0:0: +121,103,28875,2,4,L|190:103,1,40 +121,103,29125,1,4,0:0:0:0: +121,103,29250,1,4,0:0:0:0: +121,103,29375,1,4,0:0:0:0: +121,103,29500,2,4,L|190:103,1,40 +121,103,29750,1,4,0:0:0:0: +121,103,29875,1,4,0:0:0:0: +121,103,30000,1,4,0:0:0:0: +121,103,30125,2,4,L|190:103,1,40 +121,103,30375,1,4,0:0:0:0: +121,103,30500,1,4,0:0:0:0: +121,103,30625,1,4,0:0:0:0: +121,103,30750,2,4,L|190:103,1,40 +121,103,31000,1,4,0:0:0:0: +121,103,31125,1,4,0:0:0:0: +121,103,31250,1,4,0:0:0:0: +121,103,31375,2,4,L|190:103,1,40 +121,103,32500,1,4,0:0:0:0: +121,103,32625,1,6,0:0:0:0: +121,103,32750,1,4,0:0:0:0: +121,103,32875,2,4,L|190:103,1,40 +121,103,33125,1,4,0:0:0:0: +121,103,33250,1,6,0:0:0:0: +121,103,33375,1,4,0:0:0:0: +121,103,33500,2,4,L|190:103,1,40 +121,103,33750,1,4,0:0:0:0: +121,103,33875,1,6,0:0:0:0: +121,103,34000,1,4,0:0:0:0: +121,103,34125,2,4,L|190:103,1,40 +121,103,34375,1,4,0:0:0:0: +121,103,34500,1,6,0:0:0:0: +121,103,34625,1,4,0:0:0:0: +121,103,34750,2,4,L|190:103,1,40 +121,103,35000,1,4,0:0:0:0: +121,103,35125,1,6,0:0:0:0: +121,103,35250,1,4,0:0:0:0: +121,103,35375,2,4,L|190:103,1,40 +121,103,36500,1,4,0:0:0:0: +121,103,36625,1,4,0:0:0:0: +121,103,36750,1,6,0:0:0:0: +121,103,36875,2,4,L|190:103,1,40 +121,103,37125,1,4,0:0:0:0: +121,103,37250,1,4,0:0:0:0: +121,103,37375,1,6,0:0:0:0: +121,103,37500,2,4,L|190:103,1,40 +121,103,37750,1,4,0:0:0:0: +121,103,37875,1,4,0:0:0:0: +121,103,38000,1,6,0:0:0:0: +121,103,38125,2,4,L|190:103,1,40 +121,103,38375,1,4,0:0:0:0: +121,103,38500,1,4,0:0:0:0: +121,103,38625,1,6,0:0:0:0: +121,103,38750,2,4,L|190:103,1,40 +121,103,39000,1,4,0:0:0:0: +121,103,39125,1,4,0:0:0:0: +121,103,39250,1,6,0:0:0:0: +121,103,39375,2,4,L|190:103,1,40 +107,106,40500,1,4,0:0:0:0: +107,106,40625,1,4,0:0:0:0: +107,106,40750,1,6,0:0:0:0: +107,106,40875,1,6,0:0:0:0: +46,112,41000,2,4,L|214:112,1,160 +107,106,41625,1,4,0:0:0:0: +107,106,41750,1,4,0:0:0:0: +107,106,41875,1,6,0:0:0:0: +107,106,42000,1,6,0:0:0:0: +46,112,42125,2,4,L|214:112,1,160 +107,106,42750,1,4,0:0:0:0: +107,106,42875,1,4,0:0:0:0: +107,106,43000,1,6,0:0:0:0: +107,106,43125,1,6,0:0:0:0: +46,112,43250,2,4,L|214:112,1,160 +107,106,43875,1,4,0:0:0:0: +107,106,44000,1,4,0:0:0:0: +107,106,44125,1,6,0:0:0:0: +107,106,44250,1,6,0:0:0:0: +46,112,44375,2,4,L|214:112,1,160 +107,106,45000,1,4,0:0:0:0: +107,106,45125,1,4,0:0:0:0: +107,106,45250,1,6,0:0:0:0: +107,106,45375,1,6,0:0:0:0: +46,112,45500,2,4,L|214:112,1,160 +256,192,47000,12,4,47500,0:0:0:0: +256,192,47625,12,4,48000,0:0:0:0: +256,192,48125,12,4,48500,0:0:0:0: +256,192,48625,12,4,49000,0:0:0:0: +256,192,50000,12,4,50500,0:0:0:0: +183,143,50625,5,4,0:0:0:0: +256,192,50750,12,4,51250,0:0:0:0: +114,106,51375,5,4,0:0:0:0: +256,192,51625,12,4,52125,0:0:0:0: +154,143,52250,5,4,0:0:0:0: +256,192,52375,12,4,52875,0:0:0:0: +116,111,53000,5,4,0:0:0:0: diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..15326162ea --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,285 @@ +osu file format v14 + +[General] +Mode: 1 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +62500,-500,4,2,1,50,0,0 +71000,-100,4,2,1,50,0,0 + +[HitObjects] +// dd, spaced 1/4 beat apart +142,122,0,5,0,0:0:0:0: +142,122,125,1,0,0:0:0:0: +142,122,250,1,0,0:0:0:0: +142,122,375,1,0,0:0:0:0: +142,122,500,1,0,0:0:0:0: +142,122,625,1,0,0:0:0:0: +142,122,750,1,0,0:0:0:0: +142,122,875,1,0,0:0:0:0: +142,122,1000,1,0,0:0:0:0: +142,122,1125,1,0,0:0:0:0: +142,122,1250,1,0,0:0:0:0: +142,122,1375,1,0,0:0:0:0: +142,122,1500,1,0,0:0:0:0: + +// kk, spaced 1/4 beat apart +119,106,2500,1,2,0:0:0:0: +119,106,2625,1,2,0:0:0:0: +119,106,2750,1,2,0:0:0:0: +119,106,2875,1,2,0:0:0:0: +119,106,3000,1,2,0:0:0:0: +119,106,3125,1,2,0:0:0:0: +119,106,3250,1,2,0:0:0:0: +119,106,3375,1,2,0:0:0:0: +119,106,3500,1,2,0:0:0:0: +119,106,3625,1,2,0:0:0:0: +119,106,3750,1,2,0:0:0:0: +119,106,3875,1,2,0:0:0:0: +119,106,4000,1,2,0:0:0:0: + +// dk, spaced 1/4 beat apart +136,90,5000,1,0,0:0:0:0: +136,90,5125,1,2,0:0:0:0: +136,90,5250,1,0,0:0:0:0: +136,90,5375,1,2,0:0:0:0: +136,90,5500,1,0,0:0:0:0: +136,90,5625,1,2,0:0:0:0: +136,90,5750,1,0,0:0:0:0: +136,90,5875,1,2,0:0:0:0: +136,90,6000,1,0,0:0:0:0: +136,90,6125,1,2,0:0:0:0: +136,90,6250,1,0,0:0:0:0: +136,90,6375,1,2,0:0:0:0: +136,90,6500,1,0,0:0:0:0: + +// ddkk, spaced 1/4 beat apart +86,113,7500,1,0,0:0:0:0: +86,113,7625,1,0,0:0:0:0: +86,113,7750,1,2,0:0:0:0: +86,113,7875,1,2,0:0:0:0: +86,113,8000,1,0,0:0:0:0: +86,113,8125,1,0,0:0:0:0: +86,113,8250,1,2,0:0:0:0: +86,113,8375,1,2,0:0:0:0: +86,113,8500,1,0,0:0:0:0: +86,113,8625,1,0,0:0:0:0: +86,113,8750,1,2,0:0:0:0: +86,113,8875,1,2,0:0:0:0: +86,113,9000,1,0,0:0:0:0: + +// dddkkk, spaced 1/4 beat apart +146,90,10000,1,0,0:0:0:0: +146,90,10125,1,0,0:0:0:0: +146,90,10250,1,0,0:0:0:0: +146,90,10375,1,2,0:0:0:0: +146,90,10500,1,2,0:0:0:0: +146,90,10625,1,2,0:0:0:0: +146,90,10750,1,0,0:0:0:0: +146,90,10875,1,0,0:0:0:0: +146,90,11000,1,0,0:0:0:0: +146,90,11125,1,2,0:0:0:0: +146,90,11250,1,2,0:0:0:0: +146,90,11375,1,2,0:0:0:0: +146,90,11500,1,0,0:0:0:0: +146,90,11625,1,0,0:0:0:0: +146,90,11750,1,0,0:0:0:0: +146,90,11875,1,2,0:0:0:0: +146,90,12000,1,2,0:0:0:0: +146,90,12125,1,2,0:0:0:0: +146,90,12250,1,0,0:0:0:0: +146,90,12375,1,0,0:0:0:0: +146,90,12500,1,0,0:0:0:0: + +// dddk, spaced 1/4 beat apart +69,99,13500,1,0,0:0:0:0: +69,99,13625,1,0,0:0:0:0: +69,99,13750,1,0,0:0:0:0: +69,99,13875,1,2,0:0:0:0: +69,99,14000,1,0,0:0:0:0: +69,99,14125,1,0,0:0:0:0: +69,99,14250,1,0,0:0:0:0: +69,99,14375,1,2,0:0:0:0: +69,99,14500,1,0,0:0:0:0: +69,99,14625,1,0,0:0:0:0: +69,99,14750,1,0,0:0:0:0: +69,99,14875,1,2,0:0:0:0: +69,99,15000,1,0,0:0:0:0: +69,99,15125,1,0,0:0:0:0: +69,99,15250,1,0,0:0:0:0: +69,99,15375,1,2,0:0:0:0: +69,99,15500,1,0,0:0:0:0: + +// arbitrary pattern, spaced 1/4 beat apart +83,89,16500,1,0,0:0:0:0: +83,89,16625,1,2,0:0:0:0: +83,89,16750,1,2,0:0:0:0: +83,89,16875,1,0,0:0:0:0: +83,89,17000,1,0,0:0:0:0: +83,89,17125,1,0,0:0:0:0: +83,89,17250,1,2,0:0:0:0: +83,89,17375,1,2,0:0:0:0: +83,89,17500,1,2,0:0:0:0: +83,89,17625,1,2,0:0:0:0: +83,89,17750,1,0,0:0:0:0: +83,89,17875,1,0,0:0:0:0: +83,89,18000,1,0,0:0:0:0: +83,89,18125,1,0,0:0:0:0: +83,89,18250,1,0,0:0:0:0: +83,89,18375,1,2,0:0:0:0: +83,89,18500,1,2,0:0:0:0: +83,89,18625,1,2,0:0:0:0: +83,89,18750,1,2,0:0:0:0: +83,89,18875,1,0,0:0:0:0: +83,89,19000,1,0,0:0:0:0: +83,89,19125,1,0,0:0:0:0: +83,89,19250,1,0,0:0:0:0: +83,89,19375,1,2,0:0:0:0: +83,89,19500,1,2,0:0:0:0: +83,89,19625,1,0,0:0:0:0: + +// d-slider pattern, spaced 1/4 beat apart +84,122,20500,1,0,0:0:0:0: +84,122,20625,2,0,L|217:123,1,120 +84,122,21125,1,0,0:0:0:0: +84,122,21250,2,0,L|217:123,1,120 +84,122,21750,1,0,0:0:0:0: +84,122,21875,2,0,L|217:123,1,120 +84,122,22375,1,0,0:0:0:0: +84,122,22500,2,0,L|217:123,1,120 +84,122,23000,1,0,0:0:0:0: +84,122,23125,2,0,L|217:123,1,120 + +// dd-slider pattern, spaced 1/4 beat apart +99,106,24500,1,0,0:0:0:0: +99,106,24625,1,0,0:0:0:0: +99,106,24750,2,0,L|194:107,1,80 +99,106,25125,1,0,0:0:0:0: +99,106,25250,1,0,0:0:0:0: +99,106,25375,2,0,L|194:107,1,80 +99,106,25750,1,0,0:0:0:0: +99,106,25875,1,0,0:0:0:0: +99,106,26000,2,0,L|194:107,1,80 +99,106,26375,1,0,0:0:0:0: +99,106,26500,1,0,0:0:0:0: +99,106,26625,2,0,L|194:107,1,80 +99,106,27000,1,0,0:0:0:0: +99,106,27125,1,0,0:0:0:0: +99,106,27250,2,0,L|194:107,1,80 + +// ddd-slider pattern, spaced 1/4 beat apart +121,103,28500,1,0,0:0:0:0: +121,103,28625,1,0,0:0:0:0: +121,103,28750,1,0,0:0:0:0: +121,103,28875,2,0,L|190:103,1,40 +121,103,29125,1,0,0:0:0:0: +121,103,29250,1,0,0:0:0:0: +121,103,29375,1,0,0:0:0:0: +121,103,29500,2,0,L|190:103,1,40 +121,103,29750,1,0,0:0:0:0: +121,103,29875,1,0,0:0:0:0: +121,103,30000,1,0,0:0:0:0: +121,103,30125,2,0,L|190:103,1,40 +121,103,30375,1,0,0:0:0:0: +121,103,30500,1,0,0:0:0:0: +121,103,30625,1,0,0:0:0:0: +121,103,30750,2,0,L|190:103,1,40 +121,103,31000,1,0,0:0:0:0: +121,103,31125,1,0,0:0:0:0: +121,103,31250,1,0,0:0:0:0: +121,103,31375,2,0,L|190:103,1,40 + +// dkd-slider pattern, spaced 1/4 beat apart +121,103,32500,1,0,0:0:0:0: +121,103,32625,1,2,0:0:0:0: +121,103,32750,1,0,0:0:0:0: +121,103,32875,2,0,L|190:103,1,40 +121,103,33125,1,0,0:0:0:0: +121,103,33250,1,2,0:0:0:0: +121,103,33375,1,0,0:0:0:0: +121,103,33500,2,0,L|190:103,1,40 +121,103,33750,1,0,0:0:0:0: +121,103,33875,1,2,0:0:0:0: +121,103,34000,1,0,0:0:0:0: +121,103,34125,2,0,L|190:103,1,40 +121,103,34375,1,0,0:0:0:0: +121,103,34500,1,2,0:0:0:0: +121,103,34625,1,0,0:0:0:0: +121,103,34750,2,0,L|190:103,1,40 +121,103,35000,1,0,0:0:0:0: +121,103,35125,1,2,0:0:0:0: +121,103,35250,1,0,0:0:0:0: +121,103,35375,2,0,L|190:103,1,40 + +//ddk-slider pattern, spaced 1/4 beat apart +121,103,36500,1,0,0:0:0:0: +121,103,36625,1,0,0:0:0:0: +121,103,36750,1,2,0:0:0:0: +121,103,36875,2,0,L|190:103,1,40 +121,103,37125,1,0,0:0:0:0: +121,103,37250,1,0,0:0:0:0: +121,103,37375,1,2,0:0:0:0: +121,103,37500,2,0,L|190:103,1,40 +121,103,37750,1,0,0:0:0:0: +121,103,37875,1,0,0:0:0:0: +121,103,38000,1,2,0:0:0:0: +121,103,38125,2,0,L|190:103,1,40 +121,103,38375,1,0,0:0:0:0: +121,103,38500,1,0,0:0:0:0: +121,103,38625,1,2,0:0:0:0: +121,103,38750,2,0,L|190:103,1,40 +121,103,39000,1,0,0:0:0:0: +121,103,39125,1,0,0:0:0:0: +121,103,39250,1,2,0:0:0:0: +121,103,39375,2,0,L|190:103,1,40 + +//ddkk-slider pattern, spaced 1/4 beat apart +107,106,40500,1,0,0:0:0:0: +107,106,40625,1,0,0:0:0:0: +107,106,40750,1,2,0:0:0:0: +107,106,40875,1,2,0:0:0:0: +46,112,41000,2,0,L|214:112,1,160 +107,106,41625,1,0,0:0:0:0: +107,106,41750,1,0,0:0:0:0: +107,106,41875,1,2,0:0:0:0: +107,106,42000,1,2,0:0:0:0: +46,112,42125,2,0,L|214:112,1,160 +107,106,42750,1,0,0:0:0:0: +107,106,42875,1,0,0:0:0:0: +107,106,43000,1,2,0:0:0:0: +107,106,43125,1,2,0:0:0:0: +46,112,43250,2,0,L|214:112,1,160 +107,106,43875,1,0,0:0:0:0: +107,106,44000,1,0,0:0:0:0: +107,106,44125,1,2,0:0:0:0: +107,106,44250,1,2,0:0:0:0: +46,112,44375,2,0,L|214:112,1,160 +107,106,45000,1,0,0:0:0:0: +107,106,45125,1,0,0:0:0:0: +107,106,45250,1,2,0:0:0:0: +107,106,45375,1,2,0:0:0:0: +46,112,45500,2,0,L|214:112,1,160 + +// spinner-spinner pattern, spaced 1/4 beat apart +256,192,47000,12,0,47500,0:0:0:0: +256,192,47625,12,0,48000,0:0:0:0: +256,192,48125,12,0,48500,0:0:0:0: +256,192,48625,12,0,49000,0:0:0:0: + +// spinner-d pattern, spaced 1/4 beat apart +256,192,50000,12,0,50500,0:0:0:0: +183,143,50625,5,0,0:0:0:0: +256,192,50750,12,0,51250,0:0:0:0: +114,106,51375,5,0,0:0:0:0: +256,192,51625,12,0,52125,0:0:0:0: +154,143,52250,5,0,0:0:0:0: +256,192,52375,12,0,52875,0:0:0:0: +116,111,53000,5,0,0:0:0:0: From 09e717d2198925d79aaffcccad8d408bd1c2c18e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Feb 2019 15:49:51 +0900 Subject: [PATCH 12/76] Add catch difficulty calculator tests --- .../CatchDifficultyCalculatorTest.cs | 24 +++ .../Testing/Beatmaps/diffcalc-test.osu | 138 ++++++++++++++++++ .../OsuDifficultyCalculatorTest.cs | 4 +- .../TaikoDifficultyCalculatorTest.cs | 4 +- 4 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs create mode 100644 osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..91bc537902 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class CatchDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; + + [TestCase(3.8664391043534758, "diffcalc-test")] + public void Test(double expected, string name) + => base.Test(expected, name); + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new CatchRuleset(); + } +} diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..ebad654404 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,138 @@ +osu file format v14 + +[General] +StackLeniency: 0.3 +Mode: 2 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +34500,-50,4,2,1,50,0,0 + +[HitObjects] +// fruits spaced 1/1 beat apart +32,128,0,5,0,0:0:0:0: +96,128,500,1,0,0:0:0:0: +160,128,1000,1,0,0:0:0:0: +224,128,1500,1,0,0:0:0:0: +288,128,2000,1,0,0:0:0:0: +352,128,2500,1,0,0:0:0:0: +416,128,3000,1,0,0:0:0:0: +480,128,3500,1,0,0:0:0:0: + +// fruits spaced 1/2 beat apart +32,160,4500,1,0,0:0:0:0: +64,160,4750,1,0,0:0:0:0: +96,160,5000,1,0,0:0:0:0: +128,160,5250,1,0,0:0:0:0: +160,160,5500,1,0,0:0:0:0: +192,160,5750,1,0,0:0:0:0: +224,160,6000,1,0,0:0:0:0: +256,160,6250,1,0,0:0:0:0: +288,160,6500,1,0,0:0:0:0: + +// fruits spaced 1/4 beat apart +96,128,7500,1,0,0:0:0:0: +128,128,7625,1,0,0:0:0:0: +160,128,7750,1,0,0:0:0:0: +192,128,7875,1,0,0:0:0:0: +224,128,8000,1,0,0:0:0:0: +256,128,8125,1,0,0:0:0:0: +288,128,8250,1,0,0:0:0:0: +320,128,8375,1,0,0:0:0:0: +352,128,8500,1,0,0:0:0:0: + +// fruit hyperdashes, spaced 1/2 beat apart +32,160,9500,1,0,0:0:0:0: +480,160,9750,1,0,0:0:0:0: +32,160,10000,1,0,0:0:0:0: +480,160,10250,1,0,0:0:0:0: +32,160,10500,1,0,0:0:0:0: +480,160,10750,1,0,0:0:0:0: +32,160,11000,1,0,0:0:0:0: + +// fruit hyperdashes, spaced 1/4 beat apart +32,192,12000,1,0,0:0:0:0: +480,192,12125,1,0,0:0:0:0: +32,192,12250,1,0,0:0:0:0: +480,192,12375,1,0,0:0:0:0: +32,192,12500,1,0,0:0:0:0: +480,192,12625,1,0,0:0:0:0: +32,192,12750,1,0,0:0:0:0: +480,192,12875,1,0,0:0:0:0: +32,192,13000,1,0,0:0:0:0: + +// stream + hyperdash + stream, spaced 1/4 beat apart +32,192,14000,1,0,0:0:0:0: +64,192,14125,1,0,0:0:0:0: +96,192,14250,1,0,0:0:0:0: +128,192,14375,1,0,0:0:0:0: +480,192,14500,1,0,0:0:0:0: +448,192,14625,1,0,0:0:0:0: +416,192,14750,1,0,0:0:0:0: +384,192,14875,1,0,0:0:0:0: +32,192,15000,1,0,0:0:0:0: + +// basic sliders +32,192,16000,2,0,L|192:192,1,160 +224,192,17000,2,0,L|384:192,1,160 +416,192,17875,2,0,L|480:192,1,40 + +// slider hyperdashes, spaced 1/4 beat apart +32,192,19000,2,0,L|128:192,1,80 +480,192,19375,2,0,L|384:192,1,80 +352,192,19750,2,0,L|256:192,1,80 +0,192,20125,2,0,L|128:192,1,120 + +// stream + slider hyperdashes, spaced 1/4 beat apart +32,192,21500,1,0,0:0:0:0: +64,192,21625,1,0,0:0:0:0: +96,192,21750,1,0,0:0:0:0: +512,192,21875,2,0,L|320:192,1,160 +320,192,22500,1,0,0:0:0:0: +288,192,22625,1,0,0:0:0:0: +256,192,22750,1,0,0:0:0:0: +0,192,22875,2,0,L|64:192,1,40 + +// streams, spaced 1/4 beat apart +64,192,24000,1,0,0:0:0:0: +160,192,24125,1,0,0:0:0:0: +64,192,24250,1,0,0:0:0:0: +160,192,24375,1,0,0:0:0:0: +64,192,24500,1,0,0:0:0:0: +160,192,24625,1,0,0:0:0:0: +64,192,24750,1,0,0:0:0:0: +160,192,24875,1,0,0:0:0:0: +64,192,25000,1,0,0:0:0:0: +160,192,25125,1,0,0:0:0:0: +64,192,25250,1,0,0:0:0:0: +160,192,25375,1,0,0:0:0:0: +64,192,25500,1,0,0:0:0:0: + +// stream + spinner combo, spaced 1/4 beat apart +256,192,26500,12,0,27000,0:0:0:0: +128,192,27250,5,0,0:0:0:0: +128,192,27375,1,0,0:0:0:0: +160,192,27500,1,0,0:0:0:0: +192,192,27625,1,0,0:0:0:0: +256,192,27750,12,0,28500,0:0:0:0: +192,192,28625,5,0,0:0:0:0: +224,192,28750,1,0,0:0:0:0: +256,192,28875,1,0,0:0:0:0: +256,192,29000,1,0,0:0:0:0: +256,192,29125,12,0,29500,0:0:0:0: + +// long slow slider +0,192,30500,6,0,B|480:192|480:192|0:192,2,960 + +// long fast slider +0,192,37500,6,0,B|480:192|480:192|0:192,2,960 + +// long hyperdash slider +0,192,41500,2,0,P|544:192|544:192,5,480 diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 4926a81034..e55dc1f902 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -16,9 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(6.931145117263422, "diffcalc-test")] public void Test(double expected, string name) - { - base.Test(expected, name); - } + => base.Test(expected, name); protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 112834e84c..299f84fb1f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -16,9 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase(2.9811336589467095, "diffcalc-test")] [TestCase(2.9811336589467095, "diffcalc-test-strong")] public void Test(double expected, string name) - { - base.Test(expected, name); - } + => base.Test(expected, name); protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); From e319a760b807ba9795355ab200c11a59c8cc0941 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Feb 2019 16:25:44 +0900 Subject: [PATCH 13/76] Add mania difficulty calculator test --- .../ManiaDifficultyCalculatorTest.cs | 24 +++ .../Testing/Beatmaps/diffcalc-test.osu | 180 ++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..05e2df796c --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class ManiaDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + + [TestCase(2.2676066895468976, "diffcalc-test")] + public void Test(double expected, string name) + => base.Test(expected, name); + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + } +} diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..4c877c6193 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,180 @@ +osu file format v14 + +[General] +Mode: 3 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +37500,-50,4,2,1,50,0,0 +41500,-25,4,2,1,50,0,0 + +[HitObjects] +// jacks spaced 1/1 beat apart +64,192,0,1,0,0:0:0:0: +64,192,500,1,0,0:0:0:0: +64,192,1000,1,0,0:0:0:0: +64,192,1500,1,0,0:0:0:0: +64,192,2000,1,0,0:0:0:0: +64,192,2500,1,0,0:0:0:0: + +// jacks spaced 1/2 beat apart +64,192,3500,1,0,0:0:0:0: +64,192,3750,1,0,0:0:0:0: +64,192,4000,1,0,0:0:0:0: +64,192,4250,1,0,0:0:0:0: +64,192,4500,1,0,0:0:0:0: +64,192,4750,1,0,0:0:0:0: +64,192,5000,1,0,0:0:0:0: +64,192,6000,1,0,0:0:0:0: + +// doubles jacks spaced 1/2 beat apart +192,192,6000,1,0,0:0:0:0: +64,192,6250,1,0,0:0:0:0: +192,192,6250,1,0,0:0:0:0: +64,192,6500,1,0,0:0:0:0: +192,192,6500,1,0,0:0:0:0: +64,192,6750,1,0,0:0:0:0: +192,192,6750,1,0,0:0:0:0: +64,192,7000,1,0,0:0:0:0: +192,192,7000,1,0,0:0:0:0: +64,192,7250,1,0,0:0:0:0: +192,192,7250,1,0,0:0:0:0: +64,192,7500,1,0,0:0:0:0: +192,192,7500,1,0,0:0:0:0: + +// trill spaced 1/2 beat apart +64,192,8500,1,0,0:0:0:0: +192,192,8750,1,0,0:0:0:0: +64,192,9000,1,0,0:0:0:0: +192,192,9250,1,0,0:0:0:0: +64,192,9500,1,0,0:0:0:0: +192,192,9750,1,0,0:0:0:0: +64,192,10000,1,0,0:0:0:0: +192,192,10250,1,0,0:0:0:0: +64,192,10500,1,0,0:0:0:0: + +// stair spaced 1/4 apart +64,192,11500,1,0,0:0:0:0: +192,192,11625,1,0,0:0:0:0: +320,192,11750,1,0,0:0:0:0: +448,192,11875,1,0,0:0:0:0: +320,192,12000,1,0,0:0:0:0: +192,192,12125,1,0,0:0:0:0: +64,192,12250,1,0,0:0:0:0: +192,192,12375,1,0,0:0:0:0: +320,192,12500,1,0,0:0:0:0: +448,192,12625,1,0,0:0:0:0: + +// jumpstreams? +64,192,13500,1,0,0:0:0:0: +192,192,13625,1,0,0:0:0:0: +320,192,13750,1,0,0:0:0:0: +448,192,13875,1,0,0:0:0:0: +320,192,14000,1,0,0:0:0:0: +192,192,14000,1,0,0:0:0:0: +64,192,14125,1,0,0:0:0:0: +192,192,14250,1,0,0:0:0:0: +320,192,14250,1,0,0:0:0:0: +448,192,14250,1,0,0:0:0:0: +64,192,14375,1,0,0:0:0:0: +64,192,14500,1,0,0:0:0:0: +320,192,14625,1,0,0:0:0:0: +448,192,14625,1,0,0:0:0:0: +192,192,14625,1,0,0:0:0:0: +192,192,14750,1,0,0:0:0:0: +64,192,14875,1,0,0:0:0:0: +192,192,15000,1,0,0:0:0:0: +320,192,15125,1,0,0:0:0:0: +448,192,15125,1,0,0:0:0:0: + +// double... jumps? +64,192,16000,1,0,0:0:0:0: +64,192,16250,1,0,0:0:0:0: +192,192,16250,1,0,0:0:0:0: +192,192,16500,1,0,0:0:0:0: +320,192,16500,1,0,0:0:0:0: +320,192,16750,1,0,0:0:0:0: +448,192,16750,1,0,0:0:0:0: +448,192,17000,1,0,0:0:0:0: + +// notes alongside hold +64,192,18000,128,0,18500:0:0:0:0: +192,192,18000,1,0,0:0:0:0: +192,192,18250,1,0,0:0:0:0: +192,192,18500,1,0,0:0:0:0: + +// notes overlapping hold +64,192,19500,1,0,0:0:0:0: +192,192,19625,128,0,20875:0:0:0:0: +64,192,19750,1,0,0:0:0:0: +64,192,20000,1,0,0:0:0:0: +64,192,20250,1,0,0:0:0:0: +64,192,20500,1,0,0:0:0:0: +64,192,20750,1,0,0:0:0:0: +64,192,21000,1,0,0:0:0:0: + +// simultaneous holds +64,192,22000,128,0,23000:0:0:0:0: +192,192,22000,128,0,23000:0:0:0:0: +320,192,22000,128,0,23000:0:0:0:0: +448,192,22000,128,0,23000:0:0:0:0: + +// hold stairs +64,192,24500,128,0,25500:0:0:0:0: +192,192,24625,128,0,25375:0:0:0:0: +320,192,24750,128,0,25250:0:0:0:0: +448,192,24875,128,0,25125:0:0:0:0: +448,192,25375,128,0,26375:0:0:0:0: +320,192,25500,128,0,26250:0:0:0:0: +192,192,25625,128,0,26125:0:0:0:0: +64,192,25750,128,0,26000:0:0:0:0: + +// quads +64,192,26500,1,0,0:0:0:0: +64,192,27500,1,0,0:0:0:0: +192,192,27500,1,0,0:0:0:0: +320,192,27500,1,0,0:0:0:0: +448,192,27500,1,0,0:0:0:0: +64,192,27750,1,0,0:0:0:0: +192,192,27750,1,0,0:0:0:0: +320,192,27750,1,0,0:0:0:0: +448,192,27750,1,0,0:0:0:0: +64,192,28000,1,0,0:0:0:0: +192,192,28000,1,0,0:0:0:0: +320,192,28000,1,0,0:0:0:0: +448,192,28000,1,0,0:0:0:0: +64,192,28250,1,0,0:0:0:0: +192,192,28250,1,0,0:0:0:0: +320,192,28250,1,0,0:0:0:0: +448,192,28250,1,0,0:0:0:0: +64,192,28500,1,0,0:0:0:0: +192,192,28500,1,0,0:0:0:0: +320,192,28500,1,0,0:0:0:0: +448,192,28500,1,0,0:0:0:0: + +// double-trills +64,192,29500,1,0,0:0:0:0: +192,192,29500,1,0,0:0:0:0: +320,192,29625,1,0,0:0:0:0: +448,192,29625,1,0,0:0:0:0: +64,192,29750,1,0,0:0:0:0: +192,192,29750,1,0,0:0:0:0: +320,192,29875,1,0,0:0:0:0: +448,192,29875,1,0,0:0:0:0: +64,192,30000,1,0,0:0:0:0: +192,192,30000,1,0,0:0:0:0: +320,192,30125,1,0,0:0:0:0: +448,192,30125,1,0,0:0:0:0: +64,192,30250,1,0,0:0:0:0: +192,192,30250,1,0,0:0:0:0: +320,192,30375,1,0,0:0:0:0: +448,192,30375,1,0,0:0:0:0: +64,192,30500,1,0,0:0:0:0: +192,192,30500,1,0,0:0:0:0: From 3ea13b1ade846820c94bcf70cd72d8bc1f12d0f7 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Feb 2019 16:55:39 +0900 Subject: [PATCH 14/76] Fix mouse cursor appearing prematurely during startup --- osu.Game/OsuGame.cs | 5 +++++ osu.Game/Screens/Loader.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ad6411a2d6..7b4ff3d295 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -337,6 +337,11 @@ namespace osu.Game { base.LoadComplete(); + // The next time this is updated is in UpdateAfterChildren, which occurs too late and results + // in the cursor being shown for a few frames during the intro. + // This prevents the cursor from showing until we have a screen with CursorVisible = true + MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false; + // todo: all archive managers should be able to be looped here. SkinManager.PostNotification = n => notifications?.Post(n); SkinManager.GetStableStorage = GetStorageForStableInstall; diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 9703d79442..0db0f1a1a3 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + public override bool CursorVisible => false; + protected override bool AllowBackButton => false; public Loader() From 65721a01aba53c4baefcc4d0f5aa3b429b46c99b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Feb 2019 17:01:06 +0900 Subject: [PATCH 15/76] Fix regressed screen test cases --- osu.Game.Tests/Visual/TestCaseDisclaimer.cs | 15 ++------------- osu.Game.Tests/Visual/TestCaseDrawings.cs | 4 ++-- osu.Game.Tests/Visual/TestCaseMatchResults.cs | 4 ++-- osu.Game.Tests/Visual/TestCaseMultiScreen.cs | 4 ++-- .../Visual/TestCaseParallaxContainer.cs | 7 ++++++- osu.Game.Tests/Visual/TestCasePlaySongSelect.cs | 6 ++++-- osu.Game.Tests/Visual/TestCaseResults.cs | 4 ++-- osu.Game/Tests/OsuTestBrowser.cs | 8 +++++--- osu.Game/Tests/Visual/MultiplayerTestCase.cs | 2 +- 9 files changed, 26 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs index d3a53e4a05..3ceb3eb4bd 100644 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs +++ b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs @@ -2,27 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Screens.Menu; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { - public class TestCaseDisclaimer : OsuTestCase + public class TestCaseDisclaimer : ScreenTestCase { [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - new Disclaimer() - }; + LoadScreen(new Disclaimer()); } } } diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/TestCaseDrawings.cs index 51f34d54db..aad135b71f 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/TestCaseDrawings.cs @@ -9,11 +9,11 @@ using osu.Game.Screens.Tournament.Teams; namespace osu.Game.Tests.Visual { [Description("for tournament use")] - public class TestCaseDrawings : OsuTestCase + public class TestCaseDrawings : ScreenTestCase { public TestCaseDrawings() { - Add(new Drawings + LoadScreen(new Drawings { TeamList = new TestTeamList(), }); diff --git a/osu.Game.Tests/Visual/TestCaseMatchResults.cs b/osu.Game.Tests/Visual/TestCaseMatchResults.cs index 33469e74b7..582c035e82 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchResults.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchResults.cs @@ -40,10 +40,10 @@ namespace osu.Game.Tests.Visual Room.RoomID.Value = 1; Room.Name.Value = "an awesome room"; - Child = new TestMatchResults(new ScoreInfo + LoadScreen(new TestMatchResults(new ScoreInfo { User = new User { Id = 10 }, - }); + })); } private class TestMatchResults : MatchResults diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs index d83b90d6e1..fc4037f58b 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs @@ -12,7 +12,7 @@ using osu.Game.Screens.Multi.Lounge.Components; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseMultiScreen : OsuTestCase + public class TestCaseMultiScreen : ScreenTestCase { public override IReadOnlyList RequiredTypes => new[] { @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual { Multiplayer multi = new Multiplayer(); - AddStep(@"show", () => Add(multi)); + AddStep(@"show", () => LoadScreen(multi)); AddWaitStep(5); AddStep(@"exit", multi.Exit); } diff --git a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs b/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs index f9804655ea..41b029d69e 100644 --- a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseParallaxContainer.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 osu.Framework.Graphics; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; @@ -14,7 +16,10 @@ namespace osu.Game.Tests.Visual Add(parallax = new ParallaxContainer { - Child = new BackgroundScreenDefault { Alpha = 0.8f } + Child = new ScreenStack(new BackgroundScreenDefault { Alpha = 0.8f }) + { + RelativeSizeAxes = Axes.Both, + } }); AddStep("default parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT); diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index dedcc24e53..c5cc1776f8 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -12,6 +12,7 @@ using osu.Framework.Configuration; using osu.Framework.Extensions; using osu.Framework.MathUtils; using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; @@ -25,7 +26,7 @@ using osu.Game.Screens.Select.Filter; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCasePlaySongSelect : OsuTestCase + public class TestCasePlaySongSelect : ScreenTestCase { private BeatmapManager manager; @@ -107,7 +108,8 @@ namespace osu.Game.Tests.Visual Schedule(() => { manager?.Delete(manager.GetAllUsableBeatmapSets()); - Child = songSelect = new TestSongSelect(); + LoadScreen(songSelect = new TestSongSelect()); + AddUntilStep(() => songSelect.IsPresent, "wait for present"); }); } diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index 403742a7b5..c2880c1ea2 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -16,7 +16,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseResults : OsuTestCase + public class TestCaseResults : ScreenTestCase { private BeatmapManager beatmaps; @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - Add(new SoloResults(new ScoreInfo + LoadScreen(new SoloResults(new ScoreInfo { TotalScore = 2845370, Accuracy = 0.98, diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs index ae347965a9..71b0b02fa6 100644 --- a/osu.Game/Tests/OsuTestBrowser.cs +++ b/osu.Game/Tests/OsuTestBrowser.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Screens.Backgrounds; @@ -14,10 +16,10 @@ namespace osu.Game.Tests { base.LoadComplete(); - LoadComponentAsync(new BackgroundScreenDefault + LoadComponentAsync(new ScreenStack(new BackgroundScreenDefault { Colour = OsuColour.Gray(0.5f) }) { - Colour = OsuColour.Gray(0.5f), - Depth = 10 + Depth = 10, + RelativeSizeAxes = Axes.Both, }, AddInternal); // Have to construct this here, rather than in the constructor, because diff --git a/osu.Game/Tests/Visual/MultiplayerTestCase.cs b/osu.Game/Tests/Visual/MultiplayerTestCase.cs index 7e2f915179..578ef6632c 100644 --- a/osu.Game/Tests/Visual/MultiplayerTestCase.cs +++ b/osu.Game/Tests/Visual/MultiplayerTestCase.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Multiplayer; namespace osu.Game.Tests.Visual { - public abstract class MultiplayerTestCase : OsuTestCase + public abstract class MultiplayerTestCase : ScreenTestCase { [Cached] private readonly Bindable currentRoom = new Bindable(new Room()); From 5d502250d8cbdc97c8795252ea7e5049a0b76479 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Feb 2019 17:01:51 +0900 Subject: [PATCH 16/76] Fix account creation overlay not working Regressed with screen stack changes. --- osu.Game/Overlays/AccountCreationOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index dab960db25..bc780538d5 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -70,7 +70,10 @@ namespace osu.Game.Overlays Colour = Color4.Black, Alpha = 0.9f, }, - welcomeScreen = new ScreenWelcome(), + new ScreenStack(welcomeScreen = new ScreenWelcome()) + { + RelativeSizeAxes = Axes.Both, + }, } } } From fc583590d3aa4a6daf27b5cabde29b3ecbbd9715 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Feb 2019 17:40:49 +0900 Subject: [PATCH 17/76] Fix OsuGame testcase --- osu.Game.Tests/Visual/TestCaseOsuGame.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs index 16087b5ad8..c527bce683 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs +++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK.Graphics; @@ -29,7 +30,10 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - new Loader() + new ScreenStack(new Loader()) + { + RelativeSizeAxes = Axes.Both, + } }; } } From 38cf5a1ea4670d6eaa62fc8b08eaec22a02c95de Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Feb 2019 21:03:06 +0900 Subject: [PATCH 18/76] Add support for the HitCircleOverlap property in legacy skins --- osu.Game/Skinning/LegacySkin.cs | 9 ++++++++- osu.Game/Skinning/LegacySkinDecoder.cs | 3 +++ osu.Game/Skinning/SkinConfiguration.cs | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 80f79129c9..5dfefcb777 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -58,7 +58,14 @@ namespace osu.Game.Skinning componentName = "hit300"; break; case "Play/osu/number-text": - return !hasFont(Configuration.HitCircleFont) ? null : new LegacySpriteText(Textures, Configuration.HitCircleFont) { Scale = new Vector2(0.96f) }; + return !hasFont(Configuration.HitCircleFont) + ? null + : new LegacySpriteText(Textures, Configuration.HitCircleFont) + { + Scale = new Vector2(0.96f), + // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size + Spacing = new Vector2(-Configuration.HitCircleOverlap * 0.89f, 0) + }; } var texture = GetTexture(componentName); diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 44bb9c3f41..96a9116c51 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -46,6 +46,9 @@ namespace osu.Game.Skinning case "HitCirclePrefix": skin.HitCircleFont = pair.Value; break; + case "HitCircleOverlap": + skin.HitCircleOverlap = int.Parse(pair.Value); + break; } break; diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index a8091d1f36..5b832e15a3 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -22,6 +22,7 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); public string HitCircleFont { get; set; } = "default"; + public int HitCircleOverlap { get; set; } public bool? CursorExpand { get; set; } = true; } From 1c8212d510e6bb218570a2ad2fa1afb5a3abc383 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Feb 2019 21:03:55 +0900 Subject: [PATCH 19/76] Add a TestCase for looong combos --- .../TestCaseHitCircleLongCombo.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs new file mode 100644 index 0000000000..f5fe36b56a --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs @@ -0,0 +1,36 @@ +// 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; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseHitCircleLongCombo : Game.Tests.Visual.TestCasePlayer + { + public TestCaseHitCircleLongCombo() + : base(new OsuRuleset()) + { + } + + protected override IBeatmap CreateBeatmap(Ruleset ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Ruleset = ruleset.RulesetInfo + } + }; + + for (int i = 0; i < 512; i++) + beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 }); + + return beatmap; + } + } +} From 90e462309f61e30e718552829e26940c6b9733a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Feb 2019 21:16:54 +0900 Subject: [PATCH 20/76] Add newline --- osu.Game/Skinning/SkinConfiguration.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 5b832e15a3..82faec4e9d 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -22,6 +22,7 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); public string HitCircleFont { get; set; } = "default"; + public int HitCircleOverlap { get; set; } public bool? CursorExpand { get; set; } = true; From c607b8c979ec4743a7ad69c58118fd7193bbc155 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Feb 2019 21:28:59 +0900 Subject: [PATCH 21/76] 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 38ed6870ff..6b94fa4f98 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 fb8a46992e..d677ef4e21 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From aceb8c4cb08804f1f92fd47c6ae603c66c4c7a51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Feb 2019 21:50:40 +0900 Subject: [PATCH 22/76] Fix TestCasePlaySongSelect --- .../Visual/TestCasePlaySongSelect.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index c5cc1776f8..78e90987b1 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -103,22 +103,16 @@ namespace osu.Game.Tests.Visual } [SetUp] - public virtual void SetUp() - { - Schedule(() => - { - manager?.Delete(manager.GetAllUsableBeatmapSets()); - LoadScreen(songSelect = new TestSongSelect()); - AddUntilStep(() => songSelect.IsPresent, "wait for present"); - }); - } + public virtual void SetUp() => + Schedule(() => { manager?.Delete(manager.GetAllUsableBeatmapSets()); }); [Test] public void TestDummy() { + createSongSelect(); AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); - AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); + AddUntilStep(() => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap, "dummy shown on wedge"); addManyTestMaps(); AddWaitStep(3); @@ -129,6 +123,7 @@ namespace osu.Game.Tests.Visual [Test] public void TestSorting() { + createSongSelect(); addManyTestMaps(); AddWaitStep(3); @@ -144,6 +139,7 @@ namespace osu.Game.Tests.Visual [Ignore("needs fixing")] public void TestImportUnderDifferentRuleset() { + createSongSelect(); changeRuleset(2); importForRuleset(0); AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection"); @@ -152,6 +148,7 @@ namespace osu.Game.Tests.Visual [Test] public void TestImportUnderCurrentRuleset() { + createSongSelect(); changeRuleset(2); importForRuleset(2); importForRuleset(1); @@ -167,6 +164,7 @@ namespace osu.Game.Tests.Visual [Test] public void TestRulesetChangeResetsMods() { + createSongSelect(); changeRuleset(0); changeMods(new OsuModHardRock()); @@ -196,6 +194,7 @@ namespace osu.Game.Tests.Visual [Test] public void TestStartAfterUnMatchingFilterDoesNotStart() { + createSongSelect(); addManyTestMaps(); AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "has selection"); @@ -223,6 +222,12 @@ namespace osu.Game.Tests.Visual private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); + private void createSongSelect() + { + AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); + AddUntilStep(() => songSelect.IsCurrentScreen(), "wait for present"); + } + private void addManyTestMaps() { AddStep("import test maps", () => From 57fcdf9a0b44081e1c77a572f1e606e26b5c0ace Mon Sep 17 00:00:00 2001 From: miterosan Date: Fri, 15 Feb 2019 23:47:22 +0100 Subject: [PATCH 23/76] Improve the buildscript --- .gitignore | 5 +++-- .vscode/launch.json | 14 ++++++++++++++ build.ps1 | 13 ++++++++----- build.cake => build/build.cake | 20 +++++++------------- {tools => build}/cakebuild.csproj | 0 5 files changed, 32 insertions(+), 20 deletions(-) rename build.cake => build/build.cake (77%) rename {tools => build}/cakebuild.csproj (100%) diff --git a/.gitignore b/.gitignore index f95a04e517..6186cb870d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,9 @@ *.userprefs ### Cake ### -tools/* -!tools/cakebuild.csproj +tools/** +build/tools/** + # Build results bin/[Dd]ebug/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 10c6f02314..c3306c2db7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -68,6 +68,20 @@ } }, "console": "internalConsole" + }, + { + "name": "Cake: Debug Script", + "type": "coreclr", + "request": "launch", + "program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll", + "args": [ + "${workspaceRoot}/build/build.cake", + "--debug", + "--verbosity=diagnostic" + ], + "cwd": "${workspaceRoot}/build", + "stopAtEntry": true, + "externalConsole": false } ] } diff --git a/build.ps1 b/build.ps1 index 9968673c90..c6a0bf6d4a 100644 --- a/build.ps1 +++ b/build.ps1 @@ -41,27 +41,28 @@ Param( [switch]$ShowDescription, [Alias("WhatIf", "Noop")] [switch]$DryRun, - [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)] [string[]]$ScriptArgs ) Write-Host "Preparing to run build script..." # Determine the script root for resolving other paths. -if(!$PSScriptRoot){ +if(!$PSScriptRoot) { $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent } # Resolve the paths for resources used for debugging. -$TOOLS_DIR = Join-Path $PSScriptRoot "tools" -$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj" +$BUILD_DIR = Join-Path $PSScriptRoot "build" +$TOOLS_DIR = Join-Path $BUILD_DIR "tools" +$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj" # Install the required tools locally. Write-Host "Restoring cake tools..." Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null # Find the Cake executable -$CAKE_EXECUTABLE = (Get-ChildItem -Path ./tools/cake.coreclr/ -Filter Cake.dll -Recurse).FullName +$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName # Build Cake arguments $cakeArguments = @("$Script"); @@ -75,5 +76,7 @@ $cakeArguments += $ScriptArgs # Start Cake Write-Host "Running build script..." +Push-Location -Path $BUILD_DIR Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments" +Pop-Location exit $LASTEXITCODE diff --git a/build.cake b/build/build.cake similarity index 77% rename from build.cake rename to build/build.cake index bc7dfafb8c..411adea7dd 100644 --- a/build.cake +++ b/build/build.cake @@ -1,6 +1,7 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.21" #addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" +var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); /////////////////////////////////////////////////////////////////////////////// // ARGUMENTS @@ -9,30 +10,25 @@ var target = Argument("target", "Build"); var configuration = Argument("configuration", "Release"); -var osuSolution = new FilePath("./osu.sln"); +var rootDirectory = new DirectoryPath(".."); +var desktopProject = rootDirectory.CombineWithFilePath("osu.Desktop/osu.Desktop.csproj"); +var solution = rootDirectory.CombineWithFilePath("osu.sln"); /////////////////////////////////////////////////////////////////////////////// // TASKS /////////////////////////////////////////////////////////////////////////////// -Task("Restore") - .Does(() => { - DotNetCoreRestore(osuSolution.FullPath); - }); - Task("Compile") - .IsDependentOn("Restore") .Does(() => { - DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings { + DotNetCoreBuild(solution.FullPath, new DotNetCoreBuildSettings { Configuration = configuration, - NoRestore = true, }); }); Task("Test") .IsDependentOn("Compile") .Does(() => { - var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll"); + var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll"); DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings { Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx", @@ -46,9 +42,7 @@ Task("InspectCode") .WithCriteria(IsRunningOnWindows()) .IsDependentOn("Compile") .Does(() => { - var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); - - InspectCode(osuSolution, new InspectCodeSettings { + InspectCode(solution, new InspectCodeSettings { CachesHome = "inspectcode", OutputFile = "inspectcodereport.xml", }); diff --git a/tools/cakebuild.csproj b/build/cakebuild.csproj similarity index 100% rename from tools/cakebuild.csproj rename to build/cakebuild.csproj From ed67b580fa3bef31005fdb946e0e9a4ffc4e7845 Mon Sep 17 00:00:00 2001 From: miterosan Date: Sat, 16 Feb 2019 00:02:08 +0100 Subject: [PATCH 24/76] Also apply the changes to build.sh --- build.sh | 1 + build/build.cake | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index caf1702f41..d2b88c65ae 100755 --- a/build.sh +++ b/build.sh @@ -6,6 +6,7 @@ echo "Preparing to run build script..." +cd build SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) TOOLS_DIR=$SCRIPT_DIR/tools CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr" diff --git a/build/build.cake b/build/build.cake index 411adea7dd..f27f443ee9 100644 --- a/build/build.cake +++ b/build/build.cake @@ -53,7 +53,7 @@ Task("InspectCode") Task("CodeFileSanity") .Does(() => { ValidateCodeSanity(new ValidateCodeSanitySettings { - RootDirectory = ".", + RootDirectory = rootDirectory.FullPath, IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor }); }); From 134840f118f9c5730f3736bfa7b3d5d6d857909f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 Feb 2019 11:18:17 +0900 Subject: [PATCH 25/76] Fix nullref --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 822be0891b..32eea88fbc 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Multi private readonly Bindable currentRoom = new Bindable(); [Cached] - private readonly Bindable currentFilter = new Bindable(); + private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; From c09346080f492714f850aac2f004553e24c76d27 Mon Sep 17 00:00:00 2001 From: miterosan Date: Sat, 16 Feb 2019 14:51:20 +0100 Subject: [PATCH 26/76] Remove the unused desktop project variable --- build/build.cake | 1 - 1 file changed, 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index f27f443ee9..81deeb3bc7 100644 --- a/build/build.cake +++ b/build/build.cake @@ -11,7 +11,6 @@ var target = Argument("target", "Build"); var configuration = Argument("configuration", "Release"); var rootDirectory = new DirectoryPath(".."); -var desktopProject = rootDirectory.CombineWithFilePath("osu.Desktop/osu.Desktop.csproj"); var solution = rootDirectory.CombineWithFilePath("osu.sln"); /////////////////////////////////////////////////////////////////////////////// From 97086342324789b333dcc01b5cad5bd4c170ce48 Mon Sep 17 00:00:00 2001 From: miterosan Date: Sun, 17 Feb 2019 13:57:14 +0100 Subject: [PATCH 27/76] Correct the path to the cakebuild.csproj. --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index d2b88c65ae..8f1ef5b455 100755 --- a/build.sh +++ b/build.sh @@ -12,7 +12,7 @@ TOOLS_DIR=$SCRIPT_DIR/tools CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr" SCRIPT="build.cake" -CAKE_CSPROJ=$TOOLS_DIR/"cakebuild.csproj" +CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj" # Parse arguments. CAKE_ARGUMENTS=() From a8faa942a6f074e1f0b5e16886e309e648ed37b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Feb 2019 16:01:25 +0900 Subject: [PATCH 28/76] Implement new difficulty calculator structure --- .../CatchDifficultyCalculatorTest.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- ....cs => CatchLegacyDifficultyCalculator.cs} | 4 +- .../ManiaDifficultyCalculatorTest.cs | 2 +- ....cs => ManiaLegacyDifficultyCalculator.cs} | 4 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../OsuDifficultyCalculatorTest.cs | 2 +- ...or.cs => OsuLegacyDifficultyCalculator.cs} | 4 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../TaikoDifficultyCalculatorTest.cs | 2 +- ....cs => TaikoLegacyDifficultyCalculator.cs} | 4 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- ...DifficultyAdjustmentModCombinationsTest.cs | 16 +-- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- .../Difficulty/DifficultyAttributes.cs | 8 +- .../Difficulty/DifficultyCalculator.cs | 104 +++++++++++------ .../Difficulty/LegacyDifficultyCalculator.cs | 107 ++++++++++++++++++ .../Preprocessing/DifficultyHitObject.cs | 32 ++++++ osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 103 +++++++++++++++++ osu.Game/Rulesets/Difficulty/Utils/History.cs | 86 ++++++++++++++ osu.Game/Rulesets/Ruleset.cs | 2 +- .../Beatmaps/DifficultyCalculatorTest.cs | 2 +- 22 files changed, 430 insertions(+), 64 deletions(-) rename osu.Game.Rulesets.Catch/Difficulty/{CatchDifficultyCalculator.cs => CatchLegacyDifficultyCalculator.cs} (97%) rename osu.Game.Rulesets.Mania/Difficulty/{ManiaDifficultyCalculator.cs => ManiaLegacyDifficultyCalculator.cs} (97%) rename osu.Game.Rulesets.Osu/Difficulty/{OsuDifficultyCalculator.cs => OsuLegacyDifficultyCalculator.cs} (95%) rename osu.Game.Rulesets.Taiko/Difficulty/{TaikoDifficultyCalculator.cs => TaikoLegacyDifficultyCalculator.cs} (97%) create mode 100644 osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs create mode 100644 osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs create mode 100644 osu.Game/Rulesets/Difficulty/Skills/Skill.cs create mode 100644 osu.Game/Rulesets/Difficulty/Utils/History.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 91bc537902..84f4fd9c99 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(new CatchRuleset(), beatmap); protected override Ruleset CreateRuleset() => new CatchRuleset(); } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index a69070e93e..9b2bbc9bf7 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Catch public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; - public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(this, beatmap); public override int? LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs similarity index 97% rename from osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs rename to osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs index a0b813478d..0a0897d97b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Catch.UI; namespace osu.Game.Rulesets.Catch.Difficulty { - public class CatchDifficultyCalculator : DifficultyCalculator + public class CatchLegacyDifficultyCalculator : LegacyDifficultyCalculator { /// /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty private const double star_scaling_factor = 0.145; - public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + public CatchLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 05e2df796c..ef660b9ea8 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(new ManiaRuleset(), beatmap); protected override Ruleset CreateRuleset() => new ManiaRuleset(); } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs similarity index 97% rename from osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs rename to osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs index b8588dbce2..02b03aca5d 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Difficulty { - internal class ManiaDifficultyCalculator : DifficultyCalculator + internal class ManiaLegacyDifficultyCalculator : LegacyDifficultyCalculator { private const double star_scaling_factor = 0.018; @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; - public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + public ManiaLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 57728dd134..7a2a539a9d 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; - public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(this, beatmap); public override int? LegacyID => 3; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index e55dc1f902..cc46ec7be3 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(new OsuRuleset(), beatmap); protected override Ruleset CreateRuleset() => new OsuRuleset(); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs rename to osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs index 3d0a1dc266..d01f75df6b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs @@ -13,12 +13,12 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty { - public class OsuDifficultyCalculator : DifficultyCalculator + public class OsuLegacyDifficultyCalculator : LegacyDifficultyCalculator { private const int section_length = 400; private const double difficulty_multiplier = 0.0675; - public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + public OsuLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 12d0a28a8f..6fa1532580 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; - public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(this, beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 299f84fb1f..e00f3da0b7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoLegacyDifficultyCalculator(new TaikoRuleset(), beatmap); protected override Ruleset CreateRuleset() => new TaikoRuleset(); } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs similarity index 97% rename from osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs rename to osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs index 2322446666..650b367e34 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty { - internal class TaikoDifficultyCalculator : DifficultyCalculator + internal class TaikoLegacyDifficultyCalculator : LegacyDifficultyCalculator { private const double star_scaling_factor = 0.04125; @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty /// private const double decay_weight = 0.9; - public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + public TaikoLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7851a2f919..77a53858fe 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; - public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoLegacyDifficultyCalculator(this, beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 795c2244a2..f57f25e1ff 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestNoMods() { - var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(1, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -24,7 +24,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestSingleMod() { - var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(2, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -34,7 +34,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDoubleMod() { - var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(4, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleMods() { - var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDoubleIncompatibleMods() { - var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(8, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleThroughBaseType() { - var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); Assert.IsTrue(combinations[0] is ModNoMod); @@ -136,9 +136,9 @@ namespace osu.Game.Tests.NonVisual public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; } - private class TestDifficultyCalculator : DifficultyCalculator + private class TestLegacyDifficultyCalculator : LegacyDifficultyCalculator { - public TestDifficultyCalculator(params Mod[] mods) + public TestLegacyDifficultyCalculator(params Mod[] mods) : base(null, null) { DifficultyAdjustmentMods = mods; diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 0aa1697bf8..eb9e221ca4 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap }; - public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null; + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null; public override string Description => "dummy"; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index d7654462d5..b1a88b8abd 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -8,7 +8,13 @@ namespace osu.Game.Rulesets.Difficulty public class DifficultyAttributes { public readonly Mod[] Mods; - public readonly double StarRating; + + public double StarRating; + + public DifficultyAttributes(Mod[] mods) + { + Mods = mods; + } public DifficultyAttributes(Mod[] mods, double starRating) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 818d24508a..d7ae527bb1 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -1,56 +1,67 @@ -// 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 System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty { - public abstract class DifficultyCalculator + public abstract class DifficultyCalculator : LegacyDifficultyCalculator { - private readonly Ruleset ruleset; - private readonly WorkingBeatmap beatmap; + /// + /// The length of each strain section. + /// + protected virtual int SectionLength => 400; protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) { - this.ruleset = ruleset; - this.beatmap = beatmap; } - /// - /// Calculates the difficulty of the beatmap using a specific mod combination. - /// - /// The mods that should be applied to the beatmap. - /// A structure describing the difficulty of the beatmap. - public DifficultyAttributes Calculate(params Mod[] mods) + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) { - beatmap.Mods.Value = mods; - IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + var attributes = CreateDifficultyAttributes(mods); - var clock = new StopwatchClock(); - mods.OfType().ForEach(m => m.ApplyToClock(clock)); + if (!beatmap.HitObjects.Any()) + return attributes; - return Calculate(playableBeatmap, mods, clock.Rate); - } + var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, timeRate).OrderBy(h => h.BaseObject.StartTime).ToList(); + var skills = CreateSkills(); - /// - /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. - /// - /// A collection of structures describing the difficulty of the beatmap for each mod combination. - public IEnumerable CalculateAll() - { - foreach (var combination in CreateDifficultyAdjustmentModCombinations()) + double sectionLength = SectionLength * timeRate; + + // The first object doesn't generate a strain, so we begin with an incremented section end + double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; + + foreach (DifficultyHitObject h in difficultyHitObjects) { - if (combination is MultiMod multi) - yield return Calculate(multi.Mods); - else - yield return Calculate(combination); + while (h.BaseObject.StartTime > currentSectionEnd) + { + foreach (Skill s in skills) + { + s.SaveCurrentPeak(); + s.StartNewSectionFrom(currentSectionEnd); + } + + currentSectionEnd += sectionLength; + } + + foreach (Skill s in skills) + s.Process(h); } + + // The peak strain will not be saved for the last section in the above loop + foreach (Skill s in skills) + s.SaveCurrentPeak(); + + PopulateAttributes(attributes, beatmap, skills, timeRate); + + return attributes; } /// @@ -96,12 +107,33 @@ namespace osu.Game.Rulesets.Difficulty protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); /// - /// Calculates the difficulty of a using a specific combination. + /// Populates after difficulty has been processed. /// - /// The to compute the difficulty for. - /// The s that should be applied. + /// The to populate with information about the difficulty of . + /// The whose difficulty was processed. + /// The skills which processed the difficulty. /// The rate of time in . - /// A structure containing the difficulty attributes. - protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate); + protected abstract void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate); + + /// + /// Enumerates s to be processed from s in the . + /// + /// The providing the s to enumerate. + /// The rate of time in . + /// The enumerated s. + protected abstract IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate); + + /// + /// Creates the s to calculate the difficulty of s. + /// + /// The s. + protected abstract Skill[] CreateSkills(); + + /// + /// Creates an empty . + /// + /// The s which difficulty is being processed with. + /// The empty . + protected abstract DifficultyAttributes CreateDifficultyAttributes(Mod[] mods); } } diff --git a/osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs new file mode 100644 index 0000000000..15565ef847 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs @@ -0,0 +1,107 @@ +// 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.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Difficulty +{ + public abstract class LegacyDifficultyCalculator + { + private readonly Ruleset ruleset; + private readonly WorkingBeatmap beatmap; + + protected LegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + { + this.ruleset = ruleset; + this.beatmap = beatmap; + } + + /// + /// Calculates the difficulty of the beatmap using a specific mod combination. + /// + /// The mods that should be applied to the beatmap. + /// A structure describing the difficulty of the beatmap. + public DifficultyAttributes Calculate(params Mod[] mods) + { + beatmap.Mods.Value = mods; + IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + + var clock = new StopwatchClock(); + mods.OfType().ForEach(m => m.ApplyToClock(clock)); + + return Calculate(playableBeatmap, mods, clock.Rate); + } + + /// + /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. + /// + /// A collection of structures describing the difficulty of the beatmap for each mod combination. + public IEnumerable CalculateAll() + { + foreach (var combination in CreateDifficultyAdjustmentModCombinations()) + { + if (combination is MultiMod multi) + yield return Calculate(multi.Mods); + else + yield return Calculate(combination); + } + } + + /// + /// Creates all combinations which adjust the difficulty. + /// + public Mod[] CreateDifficultyAdjustmentModCombinations() + { + return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); + + IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + { + switch (currentSetCount) + { + case 0: + // Initial-case: Empty current set + yield return new ModNoMod(); + break; + case 1: + yield return currentSet.Single(); + break; + default: + yield return new MultiMod(currentSet.ToArray()); + break; + } + + // Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod + // combinations in further recursions, so a moving subset is used to eliminate this effect + for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) + { + var adjustmentMod = adjustmentSet[i]; + if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)))) + continue; + + foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1)) + yield return combo; + } + } + } + + /// + /// Retrieves all s which adjust the difficulty. + /// + protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); + + /// + /// Calculates the difficulty of a using a specific combination. + /// + /// The to compute the difficulty for. + /// The s that should be applied. + /// The rate of time in . + /// A structure containing the difficulty attributes. + protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate); + } +} diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs new file mode 100644 index 0000000000..77c9b7e47f --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Difficulty.Preprocessing +{ + public class DifficultyHitObject + { + /// + /// Milliseconds elapsed since the of the previous . + /// + public double DeltaTime { get; private set; } + + /// + /// The this refers to. + /// + public readonly HitObject BaseObject; + + /// + /// The previous to . + /// + public readonly HitObject LastObject; + + public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate) + { + BaseObject = hitObject; + LastObject = lastObject; + DeltaTime = (hitObject.StartTime - lastObject.StartTime) / timeRate; + } + } +} diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs new file mode 100644 index 0000000000..fa7aa8f637 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -0,0 +1,103 @@ +// 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 osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Utils; + +namespace osu.Game.Rulesets.Difficulty.Skills +{ + /// + /// Used to processes strain values of s, keep track of strain levels caused by the processed objects + /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. + /// + public abstract class Skill + { + /// + /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. + /// + protected abstract double SkillMultiplier { get; } + + /// + /// Determines how quickly strain decays for the given skill. + /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. + /// + protected abstract double StrainDecayBase { get; } + + /// + /// The weight by which each strain value decays. + /// + protected virtual double DecayWeight => 0.9; + + /// + /// s that were processed previously. They can affect the strain values of the following objects. + /// + protected readonly History Previous = new History(2); // Contained objects not used yet + + private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. + private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. + private readonly List strainPeaks = new List(); + + /// + /// Process a and update current strain values accordingly. + /// + public void Process(DifficultyHitObject current) + { + currentStrain *= strainDecay(current.DeltaTime); + currentStrain += StrainValueOf(current) * SkillMultiplier; + + currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); + + Previous.Push(current); + } + + /// + /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. + /// + public void SaveCurrentPeak() + { + if (Previous.Count > 0) + strainPeaks.Add(currentSectionPeak); + } + + /// + /// Sets the initial strain level for a new section. + /// + /// The beginning of the new section in milliseconds. + public void StartNewSectionFrom(double offset) + { + // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. + // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. + if (Previous.Count > 0) + currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime); + } + + /// + /// Returns the calculated difficulty value representing all processed s. + /// + public double DifficultyValue() + { + strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + double difficulty = 0; + double weight = 1; + + // Difficulty is the weighted sum of the highest strains from every section. + foreach (double strain in strainPeaks) + { + difficulty += strain * weight; + weight *= DecayWeight; + } + + return difficulty; + } + + /// + /// Calculates the strain value of a . This value is affected by previously processed objects. + /// + protected abstract double StrainValueOf(DifficultyHitObject current); + + private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); + } +} diff --git a/osu.Game/Rulesets/Difficulty/Utils/History.cs b/osu.Game/Rulesets/Difficulty/Utils/History.cs new file mode 100644 index 0000000000..d6647d5119 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Utils/History.cs @@ -0,0 +1,86 @@ +// 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; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Difficulty.Utils +{ + /// + /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. + /// Indexing starts at the top of the stack. + /// + public class History : IEnumerable + { + public int Count { get; private set; } + + private readonly T[] array; + private readonly int capacity; + private int marker; // Marks the position of the most recently added item. + + /// + /// Initializes a new instance of the History class that is empty and has the specified capacity. + /// + /// The number of items the History can hold. + public History(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(); + + this.capacity = capacity; + array = new T[capacity]; + marker = capacity; // Set marker to the end of the array, outside of the indexed range by one. + } + + /// + /// The most recently added item is returned at index 0. + /// + public T this[int i] + { + get + { + if (i < 0 || i > Count - 1) + throw new IndexOutOfRangeException(); + + i += marker; + if (i > capacity - 1) + i -= capacity; + + return array[i]; + } + } + + /// + /// Adds the item as the most recent one in the history. + /// The oldest item is disposed if the history is full. + /// + public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition. + { + if (marker == 0) + marker = capacity - 1; + else + --marker; + + array[marker] = item; + + if (Count < capacity) + ++Count; + } + + /// + /// Returns an enumerator which enumerates items in the history starting from the most recently added one. + /// + public IEnumerator GetEnumerator() + { + for (int i = marker; i < capacity; ++i) + yield return array[i]; + + if (Count == capacity) + for (int i = 0; i < marker; ++i) + yield return array[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index ffab0abebf..75643a85dc 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets /// The . public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null; - public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); + public abstract LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null; diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 108fa8ff71..85ae1958ef 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Beatmaps return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); } - protected abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); + protected abstract LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); protected abstract Ruleset CreateRuleset(); } From 8eba94e8c9a31300877fd7b5b8aa895571a35369 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:46:32 +0900 Subject: [PATCH 29/76] Implement new difficulty calculator for Rulesets.Catch --- .../CatchDifficultyCalculatorTest.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Difficulty/CatchDifficultyAttributes.cs | 4 +- .../Difficulty/CatchDifficultyCalculator.cs | 81 ++++++++++ .../Difficulty/CatchDifficultyHitObject.cs | 130 --------------- .../CatchLegacyDifficultyCalculator.cs | 148 ------------------ .../Preprocessing/CatchDifficultyHitObject.cs | 68 ++++++++ .../Difficulty/Skills/Movement.cs | 63 ++++++++ .../Objects/CatchHitObject.cs | 5 + 9 files changed, 221 insertions(+), 282 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs delete mode 100644 osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs delete mode 100644 osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs create mode 100644 osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs create mode 100644 osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 84f4fd9c99..61fb4ca5d1 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(new CatchRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); protected override Ruleset CreateRuleset() => new CatchRuleset(); } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9b2bbc9bf7..c539a47897 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Catch public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; - public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); public override int? LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index c6518cbf8a..8669543841 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty public double ApproachRate; public int MaxCombo; - public CatchDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) + public CatchDifficultyAttributes(Mod[] mods) + : base(mods) { } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs new file mode 100644 index 0000000000..99702235d5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -0,0 +1,81 @@ +// 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.Beatmaps; +using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.Difficulty.Skills; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Difficulty +{ + public class CatchDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.145; + + protected override int SectionLength => 750; + + private readonly float halfCatchWidth; + + public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); + halfCatchWidth = catcher.CatchWidth * 0.5f; + } + + protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + { + var catchAttributes = (CatchDifficultyAttributes)attributes; + + // this is the same as osu!, so there's potential to share the implementation... maybe + double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; + + catchAttributes.StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor; + catchAttributes.ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0; + catchAttributes.MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + { + CatchHitObject lastObject = null; + + foreach (var hitObject in beatmap.HitObjects.OfType()) + { + if (lastObject == null) + { + lastObject = hitObject; + continue; + } + + switch (hitObject) + { + // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. + case Fruit fruit: + yield return new CatchDifficultyHitObject(fruit, lastObject, timeRate, halfCatchWidth); + break; + case JuiceStream _: + foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) + yield return new CatchDifficultyHitObject(nested, lastObject, timeRate, halfCatchWidth); + break; + } + + lastObject = hitObject; + } + } + + protected override Skill[] CreateSkills() => new Skill[] + { + new Movement(), + }; + + protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new CatchDifficultyAttributes(mods); + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs deleted file mode 100644 index fb16e117b1..0000000000 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs +++ /dev/null @@ -1,130 +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 osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; -using osuTK; - -namespace osu.Game.Rulesets.Catch.Difficulty -{ - public class CatchDifficultyHitObject - { - internal static readonly double DECAY_BASE = 0.20; - private const float normalized_hitobject_radius = 41.0f; - private const float absolute_player_positioning_error = 16f; - private readonly float playerPositioningError; - - internal CatchHitObject BaseHitObject; - - /// - /// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy - /// - internal double Strain = 1; - - /// - /// This is required to keep track of lazy player movement (always moving only as far as necessary) - /// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated - /// - internal float PlayerPositionOffset; - internal float LastMovement; - - internal float NormalizedPosition; - internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset; - - internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf) - { - BaseHitObject = baseHitObject; - - // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. - float scalingFactor = normalized_hitobject_radius / catcherWidthHalf; - - playerPositioningError = absolute_player_positioning_error; // * scalingFactor; - NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; - } - - private const double direction_change_bonus = 12.5; - internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate) - { - // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. - // See Taiko feedback thread. - double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); - - // Update new position with lazy movement. - PlayerPositionOffset = - MathHelper.Clamp( - previousHitObject.ActualNormalizedPosition, - NormalizedPosition - (normalized_hitobject_radius - playerPositioningError), - NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player. - - NormalizedPosition; // Subtract HitObject position to obtain offset - - LastMovement = DistanceTo(previousHitObject); - double addition = spacingWeight(LastMovement); - - if (NormalizedPosition < previousHitObject.NormalizedPosition) - { - LastMovement = -LastMovement; - } - - CatchHitObject previousHitCircle = previousHitObject.BaseHitObject; - - double additionBonus = 0; - double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25)); - - // Direction changes give an extra point! - if (Math.Abs(LastMovement) > 0.1) - { - if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement)) - { - double bonus = direction_change_bonus / sqrtTime; - - // Weight bonus by how - double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError; - - // We want time to play a role twice here! - addition += bonus * bonusFactor; - - // Bonus for tougher direction switches and "almost" hyperdashes at this point - if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) - { - additionBonus += 0.3 * bonusFactor; - } - } - - // Base bonus for every movement, giving some weight to streams. - addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime; - } - - // Bonus for "almost" hyperdashes at corner points - if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) - { - if (!previousHitCircle.HyperDash) - { - additionBonus += 1.0; - } - else - { - // After a hyperdash we ARE in the correct position. Always! - PlayerPositionOffset = 0; - } - - addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); - } - - addition *= 850.0 / Math.Max(timeElapsed, 25); - - Strain = previousHitObject.Strain * decay + addition; - } - - private static double spacingWeight(float distance) - { - return Math.Pow(distance, 1.3) / 500; - } - - internal float DistanceTo(CatchDifficultyHitObject other) - { - return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition); - } - } -} diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs deleted file mode 100644 index 0a0897d97b..0000000000 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs +++ /dev/null @@ -1,148 +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 System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; - -namespace osu.Game.Rulesets.Catch.Difficulty -{ - public class CatchLegacyDifficultyCalculator : LegacyDifficultyCalculator - { - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 750; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.94; - - private const double star_scaling_factor = 0.145; - - public CatchLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) - { - if (!beatmap.HitObjects.Any()) - return new CatchDifficultyAttributes(mods, 0); - - var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); - float halfCatchWidth = catcher.CatchWidth * 0.5f; - - var difficultyHitObjects = new List(); - - foreach (var hitObject in beatmap.HitObjects) - { - switch (hitObject) - { - // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. - case Fruit fruit: - difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth)); - break; - case JuiceStream _: - difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth))); - break; - } - } - - difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - - if (!calculateStrainValues(difficultyHitObjects, timeRate)) - return new CatchDifficultyAttributes(mods, 0); - - // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; - double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor; - - return new CatchDifficultyAttributes(mods, starRating) - { - ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, - MaxCombo = difficultyHitObjects.Count - }; - } - - private bool calculateStrainValues(List objects, double timeRate) - { - CatchDifficultyHitObject lastObject = null; - - if (!objects.Any()) return false; - - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - foreach (var currentObject in objects) - { - if (lastObject != null) - currentObject.CalculateStrains(lastObject, timeRate); - - lastObject = currentObject; - } - - return true; - } - - private double calculateDifficulty(List objects, double timeRate) - { - // The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods - double actualStrainStep = strain_step * timeRate; - - // Find the highest strain value within each strain step - var highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - CatchDifficultyHitObject previousHitObject = null; - foreach (CatchDifficultyHitObject hitObject in objects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.Strain * decay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - maximumStrain = Math.Max(hitObject.Strain, maximumStrain); - - previousHitObject = hitObject; - } - - // Build the weighted sum over the highest strains for each interval - double difficulty = 0; - double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - foreach (double strain in highestStrains) - { - difficulty += weight * strain; - weight *= decay_weight; - } - - return difficulty; - } - } -} diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs new file mode 100644 index 0000000000..5dbd60d700 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -0,0 +1,68 @@ +// 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.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing +{ + public class CatchDifficultyHitObject : DifficultyHitObject + { + private const float normalized_hitobject_radius = 41.0f; + private const float absolute_player_positioning_error = 16f; + + public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject; + + public float MovementDistance { get; private set; } + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// + public readonly double StrainTime; + + private readonly float scalingFactor; + + private readonly CatchHitObject lastObject; + + public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate, float halfCatcherWidth) + : base(hitObject, lastObject, timeRate) + { + this.lastObject = (CatchHitObject)lastObject; + + // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. + scalingFactor = normalized_hitobject_radius / halfCatcherWidth; + + setDistances(); + + // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure + StrainTime = Math.Max(25, DeltaTime); + } + + private void setDistances() + { + float lastPosition = getNormalizedPosition(lastObject); + float currentPosition = getNormalizedPosition(BaseObject); + + // After a hyperdash the player is assumed to be in the correct position + if (lastObject.LazyMovementDistance != null && !lastObject.HyperDash) + lastPosition += lastObject.LazyMovementDistance.Value; + + BaseObject.LazyMovementDistance = + MathHelper.Clamp( + lastPosition, + currentPosition - (normalized_hitobject_radius - absolute_player_positioning_error), + currentPosition + (normalized_hitobject_radius - absolute_player_positioning_error)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player. + - currentPosition; // Subtract HitObject position to obtain offset + + MovementDistance = Math.Abs(currentPosition - lastPosition + BaseObject.LazyMovementDistance.Value); + + if (currentPosition < lastPosition) + MovementDistance *= -1; + } + + private float getNormalizedPosition(CatchHitObject hitObject) => hitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs new file mode 100644 index 0000000000..1626a9be1d --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -0,0 +1,63 @@ +// 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.Game.Rulesets.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; + +namespace osu.Game.Rulesets.Catch.Difficulty.Skills +{ + public class Movement : Skill + { + private const float absolute_player_positioning_error = 16f; + private const float normalized_hitobject_radius = 41.0f; + private const double direction_change_bonus = 12.5; + + protected override double SkillMultiplier => 850; + protected override double StrainDecayBase => 0.2; + + protected override double DecayWeight => 0.94; + + protected override double StrainValueOf(DifficultyHitObject current) + { + var catchCurrent = (CatchDifficultyHitObject)current; + var catchPrevious = (CatchDifficultyHitObject)Previous[0]; + + var sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); + + double distanceAddition = Math.Pow(Math.Abs(catchCurrent.MovementDistance), 1.3) / 500; + + double bonus = 0; + + if (Math.Abs(catchCurrent.MovementDistance) > 0.1) + { + if (catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) + { + double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(catchCurrent.MovementDistance)) / absolute_player_positioning_error; + + distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; + + // Bonus for tougher direction switches and "almost" hyperdashes at this point + if (catchPrevious.BaseObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) + bonus = 0.3 * bonusFactor; + } + + // Base bonus for every movement, giving some weight to streams. + distanceAddition += 7.5 * Math.Min(Math.Abs(catchCurrent.MovementDistance), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; + } + + // Bonus for "almost" hyperdashes at corner points + if (catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + { + if (!catchPrevious.BaseObject.HyperDash) + bonus += 1.0; + + distanceAddition *= 1.0 + bonus * ((10 - catchPrevious.BaseObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); + } + + return distanceAddition / catchCurrent.StrainTime; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 2153b8dc85..aff18619fb 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -50,6 +50,11 @@ namespace osu.Game.Rulesets.Catch.Objects /// public CatchHitObject HyperDashTarget; + /// + /// The minimum distance to be moved from the last to hit this . + /// + internal float? LazyMovementDistance; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); From e02ae927b31f6eccbe673164cb7367d4515e8175 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:47:31 +0900 Subject: [PATCH 30/76] Fix nullrefs --- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 1626a9be1d..8137437959 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -3,6 +3,7 @@ using System; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var catchCurrent = (CatchDifficultyHitObject)current; - var catchPrevious = (CatchDifficultyHitObject)Previous[0]; + var catchPrevious = Previous.Count > 0 ? (CatchDifficultyHitObject)Previous[0] : null; var sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills if (Math.Abs(catchCurrent.MovementDistance) > 0.1) { - if (catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) + if (catchPrevious != null && catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) { double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(catchCurrent.MovementDistance)) / absolute_player_positioning_error; @@ -49,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills } // Bonus for "almost" hyperdashes at corner points - if (catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + if (catchPrevious?.BaseObject is Fruit && catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) { if (!catchPrevious.BaseObject.HyperDash) bonus += 1.0; From f6b13ca79dd271141ba6de8d18ec1fd1b69a599b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 Feb 2019 11:11:31 +0900 Subject: [PATCH 31/76] Rewrite catch diffcalc for readability + attempt to fix --- .../Preprocessing/CatchDifficultyHitObject.cs | 41 +++--------------- .../Difficulty/Skills/Movement.cs | 42 +++++++++++++------ 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 5dbd60d700..6ce45bbbde 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -6,63 +6,34 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing { public class CatchDifficultyHitObject : DifficultyHitObject { private const float normalized_hitobject_radius = 41.0f; - private const float absolute_player_positioning_error = 16f; public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject; - public float MovementDistance { get; private set; } + public new CatchHitObject LastObject => (CatchHitObject)base.LastObject; + + public readonly float NormalizedPosition; + /// /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. /// public readonly double StrainTime; - private readonly float scalingFactor; - - private readonly CatchHitObject lastObject; - public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate, float halfCatcherWidth) : base(hitObject, lastObject, timeRate) { - this.lastObject = (CatchHitObject)lastObject; - // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. - scalingFactor = normalized_hitobject_radius / halfCatcherWidth; + var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; - setDistances(); + NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure StrainTime = Math.Max(25, DeltaTime); } - - private void setDistances() - { - float lastPosition = getNormalizedPosition(lastObject); - float currentPosition = getNormalizedPosition(BaseObject); - - // After a hyperdash the player is assumed to be in the correct position - if (lastObject.LazyMovementDistance != null && !lastObject.HyperDash) - lastPosition += lastObject.LazyMovementDistance.Value; - - BaseObject.LazyMovementDistance = - MathHelper.Clamp( - lastPosition, - currentPosition - (normalized_hitobject_radius - absolute_player_positioning_error), - currentPosition + (normalized_hitobject_radius - absolute_player_positioning_error)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player. - - currentPosition; // Subtract HitObject position to obtain offset - - MovementDistance = Math.Abs(currentPosition - lastPosition + BaseObject.LazyMovementDistance.Value); - - if (currentPosition < lastPosition) - MovementDistance *= -1; - } - - private float getNormalizedPosition(CatchHitObject hitObject) => hitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 8137437959..e06ca08fbe 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -3,10 +3,10 @@ using System; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty.Skills { @@ -21,43 +21,61 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills protected override double DecayWeight => 0.94; + private float lastPlayerPosition; + private float lastDistanceMoved; + protected override double StrainValueOf(DifficultyHitObject current) { var catchCurrent = (CatchDifficultyHitObject)current; - var catchPrevious = Previous.Count > 0 ? (CatchDifficultyHitObject)Previous[0] : null; - var sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); + float playerPosition = MathHelper.Clamp( + lastPlayerPosition, + catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error), + catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error) + ); - double distanceAddition = Math.Pow(Math.Abs(catchCurrent.MovementDistance), 1.3) / 500; + float distanceMoved = playerPosition - lastPlayerPosition; + + double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500; + double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); double bonus = 0; - if (Math.Abs(catchCurrent.MovementDistance) > 0.1) + // Direction changes give an extra point! + if (Math.Abs(distanceMoved) > 0.1) { - if (catchPrevious != null && catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) + if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) { - double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(catchCurrent.MovementDistance)) / absolute_player_positioning_error; + double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error; distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; // Bonus for tougher direction switches and "almost" hyperdashes at this point - if (catchPrevious.BaseObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) + if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) bonus = 0.3 * bonusFactor; } // Base bonus for every movement, giving some weight to streams. - distanceAddition += 7.5 * Math.Min(Math.Abs(catchCurrent.MovementDistance), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; + distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } // Bonus for "almost" hyperdashes at corner points - if (catchPrevious?.BaseObject is Fruit && catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) { - if (!catchPrevious.BaseObject.HyperDash) + if (!catchCurrent.LastObject.HyperDash) bonus += 1.0; + else + { + // After a hyperdash we ARE in the correct position. Always! + playerPosition = catchCurrent.NormalizedPosition; + } - distanceAddition *= 1.0 + bonus * ((10 - catchPrevious.BaseObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); + distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); } + lastPlayerPosition = playerPosition; + lastDistanceMoved = distanceMoved; + return distanceAddition / catchCurrent.StrainTime; } } From 83cab2ba8ab23a476132993afb21c41253bd95f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:48:19 +0900 Subject: [PATCH 32/76] Fix incorrect hitobject being used as the last hitobject --- .../Difficulty/CatchDifficultyCalculator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 99702235d5..d4110ab1e1 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -60,14 +60,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. case Fruit fruit: yield return new CatchDifficultyHitObject(fruit, lastObject, timeRate, halfCatchWidth); + lastObject = hitObject; break; case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) + { yield return new CatchDifficultyHitObject(nested, lastObject, timeRate, halfCatchWidth); + lastObject = nested; + } break; } - - lastObject = hitObject; } } From 9463475202be48b011e955532de1596bcd1eb2b9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:51:30 +0900 Subject: [PATCH 33/76] Remove now unused member --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index aff18619fb..2153b8dc85 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -50,11 +50,6 @@ namespace osu.Game.Rulesets.Catch.Objects /// public CatchHitObject HyperDashTarget; - /// - /// The minimum distance to be moved from the last to hit this . - /// - internal float? LazyMovementDistance; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); From 25d85b6eb4a53f32ff6687556d123a806e130365 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:54:21 +0900 Subject: [PATCH 34/76] Implement new difficulty calculator for Rulesets.Taiko --- .../TaikoDifficultyCalculatorTest.cs | 2 +- .../Preprocessing/TaikoDifficultyHitObject.cs | 20 +++ .../Difficulty/Skills/Strain.cs | 90 +++++++++++ .../Difficulty/TaikoDifficultyAttributes.cs | 4 +- .../Difficulty/TaikoDifficultyCalculator.cs | 56 +++++++ .../TaikoLegacyDifficultyCalculator.cs | 144 ------------------ .../Objects/TaikoHitObjectDifficulty.cs | 127 --------------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../osu.Game.Rulesets.Taiko.csproj | 3 + 9 files changed, 173 insertions(+), 275 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs delete mode 100644 osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index e00f3da0b7..16130c2c3d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoLegacyDifficultyCalculator(new TaikoRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); protected override Ruleset CreateRuleset() => new TaikoRuleset(); } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs new file mode 100644 index 0000000000..4d63d81663 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing +{ + public class TaikoDifficultyHitObject : DifficultyHitObject + { + public readonly bool HasTypeChange; + + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate) + : base(hitObject, lastObject, timeRate) + { + HasTypeChange = lastObject is RimHit != hitObject is RimHit; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs new file mode 100644 index 0000000000..c77e6a3afd --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs @@ -0,0 +1,90 @@ +// 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.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + public class Strain : Skill + { + private const double rhythm_change_base_threshold = 0.2; + private const double rhythm_change_base = 2.0; + + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.3; + + private ColourSwitch lastColourSwitch = ColourSwitch.None; + + private int sameTypeCount; + + protected override double StrainValueOf(DifficultyHitObject current) + { + double addition = 1; + + // We get an extra addition if we are not a slider or spinner + if (Previous[0].BaseObject is Hit && current.BaseObject is Hit && current.BaseObject.StartTime - Previous[0].BaseObject.StartTime < 1000) + { + if (hasRhythmChange(current)) + addition += 1; + + if (hasColourChange(current)) + addition += 0.75; + } + + double additionFactor = 1; + + // Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50 + if (current.DeltaTime < 50) + additionFactor = 0.4 + 0.6 * current.DeltaTime / 50; + + return additionFactor * addition; + } + + private bool hasRhythmChange(DifficultyHitObject current) + { + // We don't want a division by zero if some random mapper decides to put two HitObjects at the same time. + if (current.DeltaTime == 0 || Previous[0].DeltaTime == 0) + return false; + + double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime); + + if (timeElapsedRatio >= 8) + return false; + + double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; + + return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold; + } + + private bool hasColourChange(DifficultyHitObject current) + { + var taikoCurrent = (TaikoDifficultyHitObject)current; + + if (!taikoCurrent.HasTypeChange) + { + sameTypeCount++; + return false; + } + + var oldColourSwitch = lastColourSwitch; + var newColourSwitch = sameTypeCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; + + lastColourSwitch = newColourSwitch; + sameTypeCount = 1; + + // We only want a bonus if the parity of the color switch changes + return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch; + } + + private enum ColourSwitch + { + None, + Even, + Odd + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 3770f9601a..07721e2ac5 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public double GreatHitWindow; public int MaxCombo; - public TaikoDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) + public TaikoDifficultyAttributes(Mod[] mods) + : base(mods) { } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs new file mode 100644 index 0000000000..3d18274bba --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Difficulty.Skills; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty +{ + public class TaikoDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.018; + + public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + { + var taikoAttributes = (TaikoDifficultyAttributes)attributes; + + taikoAttributes.StarRating = skills.Single().DifficultyValue() * star_scaling_factor; + + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future + taikoAttributes.GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; + taikoAttributes.MaxCombo = beatmap.HitObjects.Count(h => h is Hit); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + { + for (int i = 1; i < beatmap.HitObjects.Count; i++) + yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], timeRate); + } + + protected override Skill[] CreateSkills() => new Skill[] { new Strain() }; + + protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new TaikoDifficultyAttributes(mods); + + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new TaikoModDoubleTime(), + new TaikoModHalfTime(), + new TaikoModEasy(), + new TaikoModHardRock(), + }; + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs deleted file mode 100644 index 650b367e34..0000000000 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs +++ /dev/null @@ -1,144 +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 System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.Difficulty -{ - internal class TaikoLegacyDifficultyCalculator : LegacyDifficultyCalculator - { - private const double star_scaling_factor = 0.04125; - - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 400; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.9; - - public TaikoLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) - { - if (!beatmap.HitObjects.Any()) - return new TaikoDifficultyAttributes(mods, 0); - - var difficultyHitObjects = new List(); - - foreach (var hitObject in beatmap.HitObjects) - difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject)); - - // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. - difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - - if (!calculateStrainValues(difficultyHitObjects, timeRate)) - return new TaikoDifficultyAttributes(mods, 0); - - double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; - - return new TaikoDifficultyAttributes(mods, starRating) - { - // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate, - MaxCombo = beatmap.HitObjects.Count(h => h is Hit) - }; - } - - private bool calculateStrainValues(List objects, double timeRate) - { - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - using (var hitObjectsEnumerator = objects.GetEnumerator()) - { - if (!hitObjectsEnumerator.MoveNext()) return false; - - TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current; - - // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. - while (hitObjectsEnumerator.MoveNext()) - { - var next = hitObjectsEnumerator.Current; - next?.CalculateStrains(current, timeRate); - current = next; - } - - return true; - } - } - - private double calculateDifficulty(List objects, double timeRate) - { - double actualStrainStep = strain_step * timeRate; - - // Find the highest strain value within each strain step - List highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - TaikoHitObjectDifficulty previousHitObject = null; - foreach (var hitObject in objects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.Strain * decay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - maximumStrain = Math.Max(hitObject.Strain, maximumStrain); - - previousHitObject = hitObject; - } - - // Build the weighted sum over the highest strains for each interval - double difficulty = 0; - double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - foreach (double strain in highestStrains) - { - difficulty += weight * strain; - weight *= decay_weight; - } - - return difficulty; - } - - protected override Mod[] DifficultyAdjustmentMods => new Mod[] - { - new TaikoModDoubleTime(), - new TaikoModHalfTime(), - new TaikoModEasy(), - new TaikoModHardRock(), - }; - } -} diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs deleted file mode 100644 index 46dd7aa87f..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs +++ /dev/null @@ -1,127 +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; - -namespace osu.Game.Rulesets.Taiko.Objects -{ - internal class TaikoHitObjectDifficulty - { - /// - /// Factor by how much individual / overall strain decays per second. - /// - /// - /// These values are results of tweaking a lot and taking into account general feedback. - /// - internal const double DECAY_BASE = 0.30; - - private const double type_change_bonus = 0.75; - private const double rhythm_change_bonus = 1.0; - private const double rhythm_change_base_threshold = 0.2; - private const double rhythm_change_base = 2.0; - - internal TaikoHitObject BaseHitObject; - - /// - /// Measures note density in a way - /// - internal double Strain = 1; - - private double timeElapsed; - private int sameTypeSince = 1; - - private bool isRim => BaseHitObject is RimHit; - - public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject) - { - BaseHitObject = baseHitObject; - } - - internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate) - { - // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. - // See Taiko feedback thread. - timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); - - double addition = 1; - - // Only if we are no slider or spinner we get an extra addition - if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit - && BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past - { - addition += typeChangeAddition(previousHitObject); - addition += rhythmChangeAddition(previousHitObject); - } - - double additionFactor = 1.0; - // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50 - if (timeElapsed < 50.0) - additionFactor = 0.4 + 0.6 * timeElapsed / 50.0; - - Strain = previousHitObject.Strain * decay + addition * additionFactor; - } - - private TypeSwitch lastTypeSwitchEven = TypeSwitch.None; - private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject) - { - // If we don't have the same hit type, trigger a type change! - if (previousHitObject.isRim != isRim) - { - lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd; - - // We only want a bonus if the parity of the type switch changes! - switch (previousHitObject.lastTypeSwitchEven) - { - case TypeSwitch.Even: - if (lastTypeSwitchEven == TypeSwitch.Odd) - return type_change_bonus; - break; - case TypeSwitch.Odd: - if (lastTypeSwitchEven == TypeSwitch.Even) - return type_change_bonus; - break; - } - } - // No type change? Increment counter and keep track of last type switch - else - { - lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven; - sameTypeSince = previousHitObject.sameTypeSince + 1; - } - - return 0; - } - - private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject) - { - // We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time. - if (timeElapsed == 0 || previousHitObject.timeElapsed == 0) - return 0; - - double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed); - - if (timeElapsedRatio >= 8) - return 0; - - double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; - - if (isWithinChangeThreshold(difference)) - return rhythm_change_bonus; - - return 0; - } - - private bool isWithinChangeThreshold(double value) - { - return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold; - } - - private enum TypeSwitch - { - None, - Even, - Odd - } - } -} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 77a53858fe..b4becae7c2 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; - public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoLegacyDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 002d6a7e8d..563ed2e7ca 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -10,4 +10,7 @@ + + + \ No newline at end of file From cb17cbcdc45d949a5ac610a15aa088bd7a828d9c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Feb 2019 14:06:48 +0900 Subject: [PATCH 35/76] Fix taiko nullrefing --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs index c77e6a3afd..7ff5684b86 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills double addition = 1; // We get an extra addition if we are not a slider or spinner - if (Previous[0].BaseObject is Hit && current.BaseObject is Hit && current.BaseObject.StartTime - Previous[0].BaseObject.StartTime < 1000) + if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) { if (hasRhythmChange(current)) addition += 1; @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private bool hasRhythmChange(DifficultyHitObject current) { // We don't want a division by zero if some random mapper decides to put two HitObjects at the same time. - if (current.DeltaTime == 0 || Previous[0].DeltaTime == 0) + if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0) return false; double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime); From 46b979a412b68ca2eb2016176575fb1bbc554941 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:55:20 +0900 Subject: [PATCH 36/76] Fix colour changes not being reset --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs | 13 +++++++++---- .../Difficulty/TaikoDifficultyCalculator.cs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs index 7ff5684b86..2465143b2b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private ColourSwitch lastColourSwitch = ColourSwitch.None; - private int sameTypeCount; + private int sameTypeCount = 1; protected override double StrainValueOf(DifficultyHitObject current) { @@ -28,11 +28,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills // We get an extra addition if we are not a slider or spinner if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) { - if (hasRhythmChange(current)) - addition += 1; - if (hasColourChange(current)) addition += 0.75; + + if (hasRhythmChange(current)) + addition += 1; + } + else + { + lastColourSwitch = ColourSwitch.None; + sameTypeCount = 1; } double additionFactor = 1; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 3d18274bba..1cdb3495ae 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyCalculator : DifficultyCalculator { - private const double star_scaling_factor = 0.018; + private const double star_scaling_factor = 0.04125; public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) From 7f4643a83d58e01021beae9413be8a4c61a05fc3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:55:39 +0900 Subject: [PATCH 37/76] Adjust naming --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs index 2465143b2b..c6fe273b50 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private ColourSwitch lastColourSwitch = ColourSwitch.None; - private int sameTypeCount = 1; + private int sameColourCount = 1; protected override double StrainValueOf(DifficultyHitObject current) { @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills else { lastColourSwitch = ColourSwitch.None; - sameTypeCount = 1; + sameColourCount = 1; } double additionFactor = 1; @@ -71,15 +71,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills if (!taikoCurrent.HasTypeChange) { - sameTypeCount++; + sameColourCount++; return false; } var oldColourSwitch = lastColourSwitch; - var newColourSwitch = sameTypeCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; + var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; lastColourSwitch = newColourSwitch; - sameTypeCount = 1; + sameColourCount = 1; // We only want a bonus if the parity of the color switch changes return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch; From fd702690218dbb43ae81cf33f35dddc04da0cddd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Feb 2019 16:03:28 +0900 Subject: [PATCH 38/76] Implement new difficulty calculator for Rulesets.Osu --- .../OsuDifficultyCalculatorTest.cs | 2 +- .../Difficulty/OsuDifficultyAttributes.cs | 4 +- .../Difficulty/OsuDifficultyCalculator.cs | 82 ++++++++++++++ .../OsuLegacyDifficultyCalculator.cs | 94 ---------------- .../Preprocessing/OsuDifficultyBeatmap.cs | 50 --------- .../Preprocessing/OsuDifficultyHitObject.cs | 56 +++------- .../Difficulty/Skills/Aim.cs | 34 +++--- .../Difficulty/Skills/Skill.cs | 103 ------------------ .../Difficulty/Skills/Speed.cs | 22 ++-- .../Difficulty/Utils/History.cs | 86 --------------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 11 files changed, 137 insertions(+), 398 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index cc46ec7be3..edf3f35304 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(new OsuRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); protected override Ruleset CreateRuleset() => new OsuRuleset(); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index fd54dc0260..9a9e72a056 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -14,8 +14,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double OverallDifficulty; public int MaxCombo; - public OsuDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) + public OsuDifficultyAttributes(Mod[] mods) + : base(mods) { } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs new file mode 100644 index 0000000000..97a925360e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -0,0 +1,82 @@ +// 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.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Skills; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Difficulty +{ + public class OsuDifficultyCalculator : DifficultyCalculator + { + private const double difficulty_multiplier = 0.0675; + + public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + { + var osuAttributes = (OsuDifficultyAttributes)attributes; + + double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; + + // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future + double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; + double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; + + int maxCombo = beatmap.HitObjects.Count; + // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) + maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); + + osuAttributes.StarRating = starRating; + osuAttributes.AimStrain = aimRating; + osuAttributes.SpeedStrain = speedRating; + osuAttributes.ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5; + osuAttributes.OverallDifficulty = (80 - hitWindowGreat) / 6; + osuAttributes.MaxCombo = maxCombo; + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + { + // The first jump is formed by the first two hitobjects of the map. + // If the map has less than two OsuHitObjects, the enumerator will not return anything. + for (int i = 1; i < beatmap.HitObjects.Count; i++) + { + var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null; + var last = beatmap.HitObjects[i - 1]; + var current = beatmap.HitObjects[i]; + + yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate); + } + } + + protected override Skill[] CreateSkills() => new Skill[] + { + new Aim(), + new Speed() + }; + + protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new OsuDifficultyAttributes(mods); + + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new OsuModDoubleTime(), + new OsuModHalfTime(), + new OsuModEasy(), + new OsuModHardRock(), + }; + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs deleted file mode 100644 index d01f75df6b..0000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs +++ /dev/null @@ -1,94 +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.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Difficulty.Skills; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty -{ - public class OsuLegacyDifficultyCalculator : LegacyDifficultyCalculator - { - private const int section_length = 400; - private const double difficulty_multiplier = 0.0675; - - public OsuLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) - { - if (!beatmap.HitObjects.Any()) - return new OsuDifficultyAttributes(mods, 0); - - OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast().ToList(), timeRate); - Skill[] skills = - { - new Aim(), - new Speed() - }; - - double sectionLength = section_length * timeRate; - - // The first object doesn't generate a strain, so we begin with an incremented section end - double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; - - foreach (OsuDifficultyHitObject h in difficultyBeatmap) - { - while (h.BaseObject.StartTime > currentSectionEnd) - { - foreach (Skill s in skills) - { - s.SaveCurrentPeak(); - s.StartNewSectionFrom(currentSectionEnd); - } - - currentSectionEnd += sectionLength; - } - - foreach (Skill s in skills) - s.Process(h); - } - - // The peak strain will not be saved for the last section in the above loop - foreach (Skill s in skills) - s.SaveCurrentPeak(); - - double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; - - // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future - double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; - double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; - - int maxCombo = beatmap.HitObjects.Count; - // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) - maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); - - return new OsuDifficultyAttributes(mods, starRating) - { - AimStrain = aimRating, - SpeedStrain = speedRating, - ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, - OverallDifficulty = (80 - hitWindowGreat) / 6, - MaxCombo = maxCombo - }; - } - - protected override Mod[] DifficultyAdjustmentMods => new Mod[] - { - new OsuModDoubleTime(), - new OsuModHalfTime(), - new OsuModEasy(), - new OsuModHardRock(), - }; - } -} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs deleted file mode 100644 index 068564d50c..0000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ /dev/null @@ -1,50 +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.Collections; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing -{ - /// - /// An enumerable container wrapping input as - /// which contains extra data required for difficulty calculation. - /// - public class OsuDifficultyBeatmap : IEnumerable - { - private readonly IEnumerator difficultyObjects; - - /// - /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as - /// which contains extra data required for difficulty calculation. - /// - public OsuDifficultyBeatmap(List objects, double timeRate) - { - // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases. - // This should probably happen before the objects reach the difficulty calculator. - difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate); - } - - /// - /// Returns an enumerator that enumerates all s in the . - /// - public IEnumerator GetEnumerator() => difficultyObjects; - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate) - { - // The first jump is formed by the first two hitobjects of the map. - // If the map has less than two OsuHitObjects, the enumerator will not return anything. - for (int i = 1; i < objects.Count; i++) - { - var lastLast = i > 1 ? objects[i - 2] : null; - var last = objects[i - 1]; - var current = objects[i]; - - yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 1ec12adb3b..31e69de6ab 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -1,24 +1,20 @@ -// 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 System; using System.Linq; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { - /// - /// A wrapper around extending it with additional data required for difficulty calculation. - /// - public class OsuDifficultyHitObject + public class OsuDifficultyHitObject : DifficultyHitObject { private const int normalized_radius = 52; - /// - /// The this refers to. - /// - public OsuHitObject BaseObject { get; } + protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; /// /// Normalized distance from the end position of the previous to the start position of this . @@ -30,40 +26,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double TravelDistance { get; private set; } - /// - /// Milliseconds elapsed since the StartTime of the previous . - /// - public double DeltaTime { get; private set; } - - /// - /// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms. - /// - public double StrainTime { get; private set; } - /// /// Angle the player has to take to hit this . /// Calculated as the angle between the circles (current-2, current-1, current). /// public double? Angle { get; private set; } + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms. + /// + public readonly double StrainTime; + private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; - private readonly double timeRate; - /// - /// Initializes the object calculating extra data required for difficulty calculation. - /// - public OsuDifficultyHitObject(OsuHitObject lastLastObject, OsuHitObject lastObject, OsuHitObject currentObject, double timeRate) + public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double timeRate) + : base(hitObject, lastObject, timeRate) { - this.lastLastObject = lastLastObject; - this.lastObject = lastObject; - this.timeRate = timeRate; - - BaseObject = currentObject; + this.lastLastObject = (OsuHitObject)lastLastObject; + this.lastObject = (OsuHitObject)lastObject; setDistances(); - setTimingValues(); - // Calculate angle here + + // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure + StrainTime = Math.Max(50, DeltaTime); } private void setDistances() @@ -102,14 +88,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing } } - private void setTimingValues() - { - DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate; - - // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure - StrainTime = Math.Max(50, DeltaTime); - } - private void computeSliderCursorPosition(Slider slider) { if (slider.LazyEndPosition != null) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index b5e57985e9..b2f2a3ac0b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; namespace osu.Game.Rulesets.Osu.Difficulty.Skills @@ -17,33 +19,37 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double SkillMultiplier => 26.25; protected override double StrainDecayBase => 0.15; - protected override double StrainValueOf(OsuDifficultyHitObject current) + protected override double StrainValueOf(DifficultyHitObject current) { + var osuCurrent = (OsuDifficultyHitObject)current; + double result = 0; - const double scale = 90; - - double applyDiminishingExp(double val) => Math.Pow(val, 0.99); - if (Previous.Count > 0) { - if (current.Angle != null && current.Angle.Value > angle_bonus_begin) + var osuPrevious = (OsuDifficultyHitObject)Previous[0]; + + if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin) { + const double scale = 90; + var angleBonus = Math.Sqrt( - Math.Max(Previous[0].JumpDistance - scale, 0) - * Math.Pow(Math.Sin(current.Angle.Value - angle_bonus_begin), 2) - * Math.Max(current.JumpDistance - scale, 0)); - result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, Previous[0].StrainTime); + Math.Max(osuPrevious.JumpDistance - scale, 0) + * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2) + * Math.Max(osuCurrent.JumpDistance - scale, 0)); + result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime); } } - double jumpDistanceExp = applyDiminishingExp(current.JumpDistance); - double travelDistanceExp = applyDiminishingExp(current.TravelDistance); + double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance); + double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); return Math.Max( - result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(current.StrainTime, timing_threshold), - (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / current.StrainTime + result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), + (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime ); } + + private double applyDiminishingExp(double val) => Math.Pow(val, 0.99); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs deleted file mode 100644 index 2f23552eb9..0000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs +++ /dev/null @@ -1,103 +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 osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Difficulty.Utils; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty.Skills -{ - /// - /// Used to processes strain values of s, keep track of strain levels caused by the processed objects - /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. - /// - public abstract class Skill - { - protected const double SINGLE_SPACING_THRESHOLD = 125; - protected const double STREAM_SPACING_THRESHOLD = 110; - - /// - /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. - /// - protected abstract double SkillMultiplier { get; } - - /// - /// Determines how quickly strain decays for the given skill. - /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. - /// - protected abstract double StrainDecayBase { get; } - - /// - /// s that were processed previously. They can affect the strain values of the following objects. - /// - protected readonly History Previous = new History(2); // Contained objects not used yet - - private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. - private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. - private readonly List strainPeaks = new List(); - - /// - /// Process an and update current strain values accordingly. - /// - public void Process(OsuDifficultyHitObject current) - { - currentStrain *= strainDecay(current.DeltaTime); - if (!(current.BaseObject is Spinner)) - currentStrain += StrainValueOf(current) * SkillMultiplier; - - currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); - - Previous.Push(current); - } - - /// - /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. - /// - public void SaveCurrentPeak() - { - if (Previous.Count > 0) - strainPeaks.Add(currentSectionPeak); - } - - /// - /// Sets the initial strain level for a new section. - /// - /// The beginning of the new section in milliseconds - public void StartNewSectionFrom(double offset) - { - // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. - // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. - if (Previous.Count > 0) - currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime); - } - - /// - /// Returns the calculated difficulty value representing all processed s. - /// - public double DifficultyValue() - { - strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - double difficulty = 0; - double weight = 1; - - // Difficulty is the weighted sum of the highest strains from every section. - foreach (double strain in strainPeaks) - { - difficulty += strain * weight; - weight *= 0.9; - } - - return difficulty; - } - - /// - /// Calculates the strain value of an . This value is affected by previously processed objects. - /// - protected abstract double StrainValueOf(OsuDifficultyHitObject current); - - private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); - } -} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index e78691ce53..de9a541ac9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; namespace osu.Game.Rulesets.Osu.Difficulty.Skills @@ -11,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : Skill { + private const double single_spacing_threshold = 125; + private const double angle_bonus_begin = 5 * Math.PI / 6; private const double pi_over_4 = Math.PI / 4; private const double pi_over_2 = Math.PI / 2; @@ -22,9 +26,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private const double max_speed_bonus = 45; // ~330BPM private const double speed_balancing_factor = 40; - protected override double StrainValueOf(OsuDifficultyHitObject current) + protected override double StrainValueOf(DifficultyHitObject current) { - double distance = Math.Min(SINGLE_SPACING_THRESHOLD, current.TravelDistance + current.JumpDistance); + var osuCurrent = (OsuDifficultyHitObject)current; + + double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime); double speedBonus = 1.0; @@ -32,20 +38,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); double angleBonus = 1.0; - if (current.Angle != null && current.Angle.Value < angle_bonus_begin) + if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) { - angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - current.Angle.Value)), 2) / 3.57; - if (current.Angle.Value < pi_over_2) + angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57; + if (osuCurrent.Angle.Value < pi_over_2) { angleBonus = 1.28; - if (distance < 90 && current.Angle.Value < pi_over_4) + if (distance < 90 && osuCurrent.Angle.Value < pi_over_4) angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1); else if (distance < 90) - angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - current.Angle.Value) / pi_over_4); + angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - osuCurrent.Angle.Value) / pi_over_4); } } - return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / SINGLE_SPACING_THRESHOLD, 3.5)) / current.StrainTime; + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs b/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs deleted file mode 100644 index e39351087e..0000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs +++ /dev/null @@ -1,86 +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; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Osu.Difficulty.Utils -{ - /// - /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. - /// Indexing starts at the top of the stack. - /// - public class History : IEnumerable - { - public int Count { get; private set; } - - private readonly T[] array; - private readonly int capacity; - private int marker; // Marks the position of the most recently added item. - - /// - /// Initializes a new instance of the History class that is empty and has the specified capacity. - /// - /// The number of items the History can hold. - public History(int capacity) - { - if (capacity < 0) - throw new ArgumentOutOfRangeException(); - - this.capacity = capacity; - array = new T[capacity]; - marker = capacity; // Set marker to the end of the array, outside of the indexed range by one. - } - - /// - /// The most recently added item is returned at index 0. - /// - public T this[int i] - { - get - { - if (i < 0 || i > Count - 1) - throw new IndexOutOfRangeException(); - - i += marker; - if (i > capacity - 1) - i -= capacity; - - return array[i]; - } - } - - /// - /// Adds the item as the most recent one in the history. - /// The oldest item is disposed if the history is full. - /// - public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition. - { - if (marker == 0) - marker = capacity - 1; - else - --marker; - - array[marker] = item; - - if (Count < capacity) - ++Count; - } - - /// - /// Returns an enumerator which enumerates items in the history starting from the most recently added one. - /// - public IEnumerator GetEnumerator() - { - for (int i = marker; i < capacity; ++i) - yield return array[i]; - - if (Count == capacity) - for (int i = 0; i < marker; ++i) - yield return array[i]; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6fa1532580..aff91e0dcb 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; - public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuLegacyDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score); From c930cc5fb548760654f7dc9c7088fedcd309da1c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Feb 2019 12:11:03 +0900 Subject: [PATCH 39/76] Fix incorrect OsuDifficultyHitObject instantiation --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 97a925360e..13d1621a39 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty var last = beatmap.HitObjects[i - 1]; var current = beatmap.HitObjects[i]; - yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate); + yield return new OsuDifficultyHitObject(current, lastLast, last, timeRate); } } From 659ec267b6d582bbe2f00a38078590f49ccd9045 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:58:33 +0900 Subject: [PATCH 40/76] Fix spinners increasing strain --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 4 ++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index b2f2a3ac0b..e74f4933b2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -5,6 +5,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -21,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { + if (current.BaseObject is Spinner) + return 0; + var osuCurrent = (OsuDifficultyHitObject)current; double result = 0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index de9a541ac9..46a81a9480 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -5,6 +5,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { + if (current.BaseObject is Spinner) + return 0; + var osuCurrent = (OsuDifficultyHitObject)current; double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); From 68725dc005f85773144031367904f618f9653931 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 13 Feb 2019 15:49:30 +0900 Subject: [PATCH 41/76] Implement new difficulty calculator for Rulesets.Mania --- .../ManiaDifficultyCalculatorTest.cs | 2 +- .../Difficulty/ManiaDifficultyAttributes.cs | 4 +- .../Difficulty/ManiaDifficultyCalculator.cs | 96 ++++++++++ .../ManiaLegacyDifficultyCalculator.cs | 173 ------------------ .../Preprocessing/ManiaDifficultyHitObject.cs | 19 ++ .../Difficulty/Skills/Individual.cs | 47 +++++ .../Difficulty/Skills/Overall.cs | 56 ++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Objects/ManiaHitObjectDifficulty.cs | 112 ------------ 9 files changed, 222 insertions(+), 289 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs delete mode 100644 osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs create mode 100644 osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs create mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs create mode 100644 osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs delete mode 100644 osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index ef660b9ea8..a5c7e051d3 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(new ManiaRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap); protected override Ruleset CreateRuleset() => new ManiaRuleset(); } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 2f614ea14b..4aa6cd730d 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public double GreatHitWindow; - public ManiaDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) + public ManiaDifficultyAttributes(Mod[] mods) + : base(mods) { } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs new file mode 100644 index 0000000000..0abb339607 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Difficulty +{ + public class ManiaDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.018; + + private int columnCount; + + private readonly bool isForCurrentRuleset; + + public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); + } + + protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + { + var maniaAttributes = (ManiaDifficultyAttributes)attributes; + + var overallStrain = skills.OfType().Single().DifficultyValue(); + var highestIndividual = skills.OfType().Max(s => s.DifficultyValue()); + + maniaAttributes.StarRating = (overallStrain + highestIndividual) * star_scaling_factor; + + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future + maniaAttributes.GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + { + columnCount = ((ManiaBeatmap)beatmap).TotalColumns; + + for (int i = 1; i < beatmap.HitObjects.Count; i++) + yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], timeRate); + } + + protected override Skill[] CreateSkills() + { + var skills = new List { new Overall(columnCount) }; + + for (int i = 0; i < columnCount; i++) + skills.Add(new Individual(i, columnCount)); + + return skills.ToArray(); + } + + protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new ManiaDifficultyAttributes(mods); + + protected override Mod[] DifficultyAdjustmentMods + { + get + { + var mods = new Mod[] + { + new ManiaModDoubleTime(), + new ManiaModHalfTime(), + new ManiaModEasy(), + new ManiaModHardRock(), + }; + + if (isForCurrentRuleset) + return mods; + + // if we are a convert, we can be played in any key mod. + return mods.Concat(new Mod[] + { + new ManiaModKey1(), + new ManiaModKey2(), + new ManiaModKey3(), + new ManiaModKey4(), + new ManiaModKey5(), + new ManiaModKey6(), + new ManiaModKey7(), + new ManiaModKey8(), + new ManiaModKey9(), + }).ToArray(); + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs deleted file mode 100644 index 02b03aca5d..0000000000 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs +++ /dev/null @@ -1,173 +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 System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Difficulty -{ - internal class ManiaLegacyDifficultyCalculator : LegacyDifficultyCalculator - { - private const double star_scaling_factor = 0.018; - - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 400; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.9; - - private readonly bool isForCurrentRuleset; - - public ManiaLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); - } - - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) - { - if (!beatmap.HitObjects.Any()) - return new ManiaDifficultyAttributes(mods, 0); - - var difficultyHitObjects = new List(); - - int columnCount = ((ManiaBeatmap)beatmap).TotalColumns; - - // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. - // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change - difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime)); - - if (!calculateStrainValues(difficultyHitObjects, timeRate)) - return new ManiaDifficultyAttributes(mods, 0); - - double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; - - return new ManiaDifficultyAttributes(mods, starRating) - { - // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate - }; - } - - private bool calculateStrainValues(List objects, double timeRate) - { - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - using (var hitObjectsEnumerator = objects.GetEnumerator()) - { - if (!hitObjectsEnumerator.MoveNext()) - return false; - - ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current; - - // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. - while (hitObjectsEnumerator.MoveNext()) - { - var next = hitObjectsEnumerator.Current; - next?.CalculateStrains(current, timeRate); - current = next; - } - - return true; - } - } - - private double calculateDifficulty(List objects, double timeRate) - { - double actualStrainStep = strain_step * timeRate; - - // Find the highest strain value within each strain step - List highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - ManiaHitObjectDifficulty previousHitObject = null; - foreach (var hitObject in objects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - double strain = hitObject.IndividualStrain + hitObject.OverallStrain; - maximumStrain = Math.Max(strain, maximumStrain); - - previousHitObject = hitObject; - } - - // Build the weighted sum over the highest strains for each interval - double difficulty = 0; - double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - foreach (double strain in highestStrains) - { - difficulty += weight * strain; - weight *= decay_weight; - } - - return difficulty; - } - - protected override Mod[] DifficultyAdjustmentMods - { - get - { - var mods = new Mod[] - { - new ManiaModDoubleTime(), - new ManiaModHalfTime(), - new ManiaModEasy(), - new ManiaModHardRock(), - }; - - if (isForCurrentRuleset) - return mods; - - // if we are a convert, we can be played in any key mod. - return mods.Concat(new Mod[] - { - new ManiaModKey1(), - new ManiaModKey2(), - new ManiaModKey3(), - new ManiaModKey4(), - new ManiaModKey5(), - new ManiaModKey6(), - new ManiaModKey7(), - new ManiaModKey8(), - new ManiaModKey9(), - }).ToArray(); - } - } - } -} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs new file mode 100644 index 0000000000..aa823e7586 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Preprocessing +{ + public class ManiaDifficultyHitObject : DifficultyHitObject + { + public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject; + + public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate) + : base(hitObject, lastObject, timeRate) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs new file mode 100644 index 0000000000..059cd39641 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs @@ -0,0 +1,47 @@ +// 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 osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Skills +{ + public class Individual : Skill + { + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.125; + + private readonly double[] holdEndTimes; + + private readonly int column; + + public Individual(int column, int columnCount) + { + this.column = column; + + holdEndTimes = new double[columnCount]; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + var maniaCurrent = (ManiaDifficultyHitObject)current; + var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + + try + { + if (maniaCurrent.BaseObject.Column != column) + return 0; + + // We give a slight bonus if something is held meanwhile + return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2; + } + finally + { + holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs new file mode 100644 index 0000000000..ed25173d38 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Skills +{ + public class Overall : Skill + { + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.3; + + private readonly double[] holdEndTimes; + + private readonly int columnCount; + + public Overall(int columnCount) + { + this.columnCount = columnCount; + + holdEndTimes = new double[columnCount]; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + var maniaCurrent = (ManiaDifficultyHitObject)current; + var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + + double holdFactor = 1.0; // Factor in case something else is held + double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly + + for (int i = 0; i < columnCount; i++) + { + // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... + if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i]) + holdAddition = 1.0; + + // ... this addition only is valid if there is _no_ other note with the same ending. + // Releasing multiple notes at the same time is just as easy as releasing one + if (endTime == holdEndTimes[i]) + holdAddition = 0; + + // We give a slight bonus if something is held meanwhile + if (holdEndTimes[i] > endTime) + holdFactor = 1.25; + } + + holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; + + return (1 + holdAddition) * holdFactor; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 7a2a539a9d..d86ee19802 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; - public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaLegacyDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap); public override int? LegacyID => 3; diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs deleted file mode 100644 index b6ea8c8665..0000000000 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs +++ /dev/null @@ -1,112 +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 osu.Game.Rulesets.Objects.Types; -using System; - -namespace osu.Game.Rulesets.Mania.Objects -{ - internal class ManiaHitObjectDifficulty - { - /// - /// Factor by how much individual / overall strain decays per second. - /// - /// - /// These values are results of tweaking a lot and taking into account general feedback. - /// - internal const double INDIVIDUAL_DECAY_BASE = 0.125; - internal const double OVERALL_DECAY_BASE = 0.30; - - internal ManiaHitObject BaseHitObject; - - private readonly int beatmapColumnCount; - - private readonly double endTime; - private readonly double[] heldUntil; - - /// - /// Measures jacks or more generally: repeated presses of the same button - /// - private readonly double[] individualStrains; - - internal double IndividualStrain - { - get - { - return individualStrains[BaseHitObject.Column]; - } - - set - { - individualStrains[BaseHitObject.Column] = value; - } - } - - /// - /// Measures note density in a way - /// - internal double OverallStrain = 1; - - public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount) - { - BaseHitObject = baseHitObject; - - endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime; - - beatmapColumnCount = columnCount; - heldUntil = new double[beatmapColumnCount]; - individualStrains = new double[beatmapColumnCount]; - - for (int i = 0; i < beatmapColumnCount; ++i) - { - individualStrains[i] = 0; - heldUntil[i] = 0; - } - } - - internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate) - { - // TODO: Factor in holds - double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000); - double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000); - - double holdFactor = 1.0; // Factor to all additional strains in case something else is held - double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly - - // Fill up the heldUntil array - for (int i = 0; i < beatmapColumnCount; ++i) - { - heldUntil[i] = previousHitObject.heldUntil[i]; - - // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... - if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i]) - { - holdAddition = 1.0; - } - - // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 - if (endTime == heldUntil[i]) - { - holdAddition = 0; - } - - // We give a slight bonus to everything if something is held meanwhile - if (heldUntil[i] > endTime) - { - holdFactor = 1.25; - } - - // Decay individual strains - individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay; - } - - heldUntil[BaseHitObject.Column] = endTime; - - // Increase individual strain in own column - IndividualStrain += 2.0 * holdFactor; - - OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor; - } - } -} From 9cce9ce97c7bfda1bfe328dee6be550ce2fde57c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 15:00:32 +0900 Subject: [PATCH 42/76] Consider aggregate peaks --- .../Difficulty/ManiaDifficultyCalculator.cs | 37 +++++++++++++++++-- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 6 +++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 0abb339607..523ac46515 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -33,15 +33,44 @@ namespace osu.Game.Rulesets.Mania.Difficulty { var maniaAttributes = (ManiaDifficultyAttributes)attributes; - var overallStrain = skills.OfType().Single().DifficultyValue(); - var highestIndividual = skills.OfType().Max(s => s.DifficultyValue()); - - maniaAttributes.StarRating = (overallStrain + highestIndividual) * star_scaling_factor; + maniaAttributes.StarRating = difficultyValue(skills) * star_scaling_factor; // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future maniaAttributes.GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; } + private double difficultyValue(Skill[] skills) + { + // Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section + var overall = skills.OfType().Single(); + var aggregatePeaks = new List(Enumerable.Repeat(0.0, overall.StrainPeaks.Count)); + + foreach (var individual in skills.OfType()) + { + for (int i = 0; i < individual.StrainPeaks.Count; i++) + { + double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i]; + + if (aggregate > aggregatePeaks[i]) + aggregatePeaks[i] = aggregate; + } + } + + aggregatePeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + double difficulty = 0; + double weight = 1; + + // Difficulty is the weighted sum of the highest strains from every section. + foreach (double strain in aggregatePeaks) + { + difficulty += strain * weight; + weight *= 0.9; + } + + return difficulty; + } + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) { columnCount = ((ManiaBeatmap)beatmap).TotalColumns; diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index fa7aa8f637..380531595a 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -14,6 +14,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public abstract class Skill { + /// + /// The peak strain for each section of the beatmap. + /// + public IList StrainPeaks => strainPeaks; + /// /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. /// @@ -37,6 +42,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. + private readonly List strainPeaks = new List(); /// From b47ced8c583d88cbec7fd5c89adc4f58ca3254b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 15:01:14 +0900 Subject: [PATCH 43/76] Fix failing test --- osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index a5c7e051d3..61ee322ce2 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.2676066895468976, "diffcalc-test")] + [TestCase(2.3683365342338796d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); From ddc1ad848e3334f68d0d56ae2ffc0234f75d15b2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 15:02:09 +0900 Subject: [PATCH 44/76] Fix failing test --- .../TaikoDifficultyCalculatorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 16130c2c3d..a26b184766 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.9811336589467095, "diffcalc-test")] - [TestCase(2.9811336589467095, "diffcalc-test-strong")] + [TestCase(2.9811338051242915d, "diffcalc-test")] + [TestCase(2.9811338051242915d, "diffcalc-test-strong")] public void Test(double expected, string name) => base.Test(expected, name); From 20f91106d9988154252a681d15841e3683594f47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 15:02:46 +0900 Subject: [PATCH 45/76] Fix failing test --- 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 61fb4ca5d1..c9831aad6d 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(3.8664391043534758, "diffcalc-test")] + [TestCase(3.8701854263563118d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); From 133c002d02829038d6b796dc9a4e73b9beb9e789 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Feb 2019 12:12:21 +0900 Subject: [PATCH 46/76] Fix test dlls being loaded as actual rulesets (and failing) --- osu.Game/Rulesets/RulesetStore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5283c5c3cf..0ebadd73d2 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -22,7 +22,8 @@ namespace osu.Game.Rulesets { AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; - foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll")) + foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll") + .Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); } @@ -124,7 +125,7 @@ namespace osu.Game.Rulesets } catch (Exception e) { - Logger.Error(e, "Failed to load ruleset"); + Logger.Error(e, $"Failed to load ruleset {filename}"); } } } From af0bb4d5e8cdb14744c7330276c115c53af55a16 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 13:40:39 +0900 Subject: [PATCH 47/76] Remove mods from constructor --- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 5 ++--- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index b1a88b8abd..d808ee528e 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -7,13 +7,12 @@ namespace osu.Game.Rulesets.Difficulty { public class DifficultyAttributes { - public readonly Mod[] Mods; + public Mod[] Mods; public double StarRating; - public DifficultyAttributes(Mod[] mods) + public DifficultyAttributes() { - Mods = mods; } public DifficultyAttributes(Mod[] mods, double starRating) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index d7ae527bb1..30fc698664 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -25,7 +25,8 @@ namespace osu.Game.Rulesets.Difficulty protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) { - var attributes = CreateDifficultyAttributes(mods); + var attributes = CreateDifficultyAttributes(); + attributes.Mods = mods; if (!beatmap.HitObjects.Any()) return attributes; @@ -132,8 +133,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Creates an empty . /// - /// The s which difficulty is being processed with. /// The empty . - protected abstract DifficultyAttributes CreateDifficultyAttributes(Mod[] mods); + protected abstract DifficultyAttributes CreateDifficultyAttributes(); } } From 3784b673aec8a3139c622fb9935566834ee03756 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 13:51:19 +0900 Subject: [PATCH 48/76] History -> LimitedCapacityStack + re-xmldoc --- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 2 +- .../{History.cs => LimitedCapacityStack.cs} | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) rename osu.Game/Rulesets/Difficulty/Utils/{History.cs => LimitedCapacityStack.cs} (69%) diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index fa7aa8f637..72bb686260 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// s that were processed previously. They can affect the strain values of the following objects. /// - protected readonly History Previous = new History(2); // Contained objects not used yet + protected readonly LimitedCapacityStack Previous = new LimitedCapacityStack(2); // Contained objects not used yet private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. diff --git a/osu.Game/Rulesets/Difficulty/Utils/History.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs similarity index 69% rename from osu.Game/Rulesets/Difficulty/Utils/History.cs rename to osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs index d6647d5119..95b7d9b19d 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/History.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs @@ -8,11 +8,13 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Difficulty.Utils { /// - /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. - /// Indexing starts at the top of the stack. + /// An indexed stack with limited depth. Indexing starts at the top of the stack. /// - public class History : IEnumerable + public class LimitedCapacityStack : IEnumerable { + /// + /// The number of elements in the stack. + /// public int Count { get; private set; } private readonly T[] array; @@ -20,10 +22,10 @@ namespace osu.Game.Rulesets.Difficulty.Utils private int marker; // Marks the position of the most recently added item. /// - /// Initializes a new instance of the History class that is empty and has the specified capacity. + /// Constructs a new . /// - /// The number of items the History can hold. - public History(int capacity) + /// The number of items the stack can hold. + public LimitedCapacityStack(int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException(); @@ -34,8 +36,9 @@ namespace osu.Game.Rulesets.Difficulty.Utils } /// - /// The most recently added item is returned at index 0. + /// Retrieves the item at an index in the stack. /// + /// The index of the item to retrieve. The top of the stack is returned at index 0. public T this[int i] { get @@ -52,11 +55,12 @@ namespace osu.Game.Rulesets.Difficulty.Utils } /// - /// Adds the item as the most recent one in the history. - /// The oldest item is disposed if the history is full. + /// Pushes an item to this . /// - public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition. + /// The item to push. + public void Push(T item) { + // Overwrite the oldest item instead of shifting every item by one with every addition. if (marker == 0) marker = capacity - 1; else From 9d8ba4073c8eb5f29674cc0539961904620f5f7c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 14:17:23 +0900 Subject: [PATCH 49/76] Add tests for LimitedCapacityStack --- .../NonVisual/LimitedCapacityStackTest.cs | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs new file mode 100644 index 0000000000..1c78b63499 --- /dev/null +++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.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 NUnit.Framework; +using osu.Game.Rulesets.Difficulty.Utils; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class LimitedCapacityStackTest + { + private const int capacity = 3; + + private LimitedCapacityStack stack; + + [SetUp] + public void Setup() + { + stack = new LimitedCapacityStack(capacity); + } + + [Test] + public void TestEmptyStack() + { + Assert.AreEqual(0, stack.Count); + + Assert.Throws(() => + { + int unused = stack[0]; + }); + + int count = 0; + foreach (var unused in stack) + count++; + + Assert.AreEqual(0, count); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + public void TestInRangeElements(int count) + { + // e.g. 0 -> 1 -> 2 + for (int i = 0; i < count; i++) + stack.Push(i); + + Assert.AreEqual(count, stack.Count); + + // e.g. 2 -> 1 -> 0 (reverse order) + for (int i = 0; i < stack.Count; i++) + Assert.AreEqual(count - 1 - i, stack[i]); + + // e.g. indices 3, 4, 5, 6 (out of range) + for (int i = stack.Count; i < stack.Count + capacity; i++) + { + Assert.Throws(() => + { + int unused = stack[i]; + }); + } + } + + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + public void TestOverflowElements(int count) + { + // e.g. 0 -> 1 -> 2 -> 3 + for (int i = 0; i < count; i++) + stack.Push(i); + + Assert.AreEqual(capacity, stack.Count); + + // e.g. 3 -> 2 -> 1 (reverse order) + for (int i = 0; i < stack.Count; i++) + Assert.AreEqual(count - 1 - i, stack[i]); + + // e.g. indices 3, 4, 5, 6 (out of range) + for (int i = stack.Count; i < stack.Count + capacity; i++) + { + Assert.Throws(() => + { + int unused = stack[i]; + }); + } + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + public void TestEnumerator(int count) + { + // e.g. 0 -> 1 -> 2 -> 3 + for (int i = 0; i < count; i++) + stack.Push(i); + + int enumeratorCount = 0; + int expectedValue = count - 1; + + foreach (var item in stack) + { + Assert.AreEqual(expectedValue, item); + enumeratorCount++; + expectedValue--; + } + + Assert.AreEqual(stack.Count, enumeratorCount); + } + } +} From 93b7b51d0a018dadbe52bff79c9d1ab69a3ce4dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 14:29:23 +0900 Subject: [PATCH 50/76] timeRate -> clockRate --- .../CatchLegacyDifficultyCalculator.cs | 8 ++++---- .../ManiaLegacyDifficultyCalculator.cs | 8 ++++---- .../Difficulty/OsuLegacyDifficultyCalculator.cs | 10 +++++----- .../TaikoLegacyDifficultyCalculator.cs | 8 ++++---- .../DifficultyAdjustmentModCombinationsTest.cs | 2 +- .../Rulesets/Difficulty/DifficultyCalculator.cs | 16 ++++++++-------- .../Difficulty/LegacyDifficultyCalculator.cs | 4 ++-- .../Preprocessing/DifficultyHitObject.cs | 4 ++-- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs index 0a0897d97b..e1f17f309b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty { } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { if (!beatmap.HitObjects.Any()) return new CatchDifficultyAttributes(mods, 0); @@ -59,12 +59,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - if (!calculateStrainValues(difficultyHitObjects, timeRate)) + if (!calculateStrainValues(difficultyHitObjects, clockRate)) return new CatchDifficultyAttributes(mods, 0); // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; - double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor; + double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, clockRate)) * star_scaling_factor; return new CatchDifficultyAttributes(mods, starRating) { diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs index 02b03aca5d..833454bf32 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyDifficultyCalculator.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { if (!beatmap.HitObjects.Any()) return new ManiaDifficultyAttributes(mods, 0); @@ -50,15 +50,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime)); - if (!calculateStrainValues(difficultyHitObjects, timeRate)) + if (!calculateStrainValues(difficultyHitObjects, clockRate)) return new ManiaDifficultyAttributes(mods, 0); - double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; + double starRating = calculateDifficulty(difficultyHitObjects, clockRate) * star_scaling_factor; return new ManiaDifficultyAttributes(mods, starRating) { // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate + GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs index d01f75df6b..137630fb85 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuLegacyDifficultyCalculator.cs @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty { } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { if (!beatmap.HitObjects.Any()) return new OsuDifficultyAttributes(mods, 0); - OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast().ToList(), timeRate); + OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast().ToList(), clockRate); Skill[] skills = { new Aim(), new Speed() }; - double sectionLength = section_length * timeRate; + double sectionLength = section_length * clockRate; // The first object doesn't generate a strain, so we begin with an incremented section end double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; @@ -66,8 +66,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future - double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; - double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; + double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate; + double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs index 650b367e34..22ee39541b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyDifficultyCalculator.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { if (!beatmap.HitObjects.Any()) return new TaikoDifficultyAttributes(mods, 0); @@ -46,15 +46,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - if (!calculateStrainValues(difficultyHitObjects, timeRate)) + if (!calculateStrainValues(difficultyHitObjects, clockRate)) return new TaikoDifficultyAttributes(mods, 0); - double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; + double starRating = calculateDifficulty(difficultyHitObjects, clockRate) * star_scaling_factor; return new TaikoDifficultyAttributes(mods, starRating) { // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate, + GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit) }; } diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index f57f25e1ff..cc73cd54a2 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -146,7 +146,7 @@ namespace osu.Game.Tests.NonVisual protected override Mod[] DifficultyAdjustmentMods { get; } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException(); + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) => throw new NotImplementedException(); } } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 30fc698664..ecfca9c589 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Difficulty { } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { var attributes = CreateDifficultyAttributes(); attributes.Mods = mods; @@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Difficulty if (!beatmap.HitObjects.Any()) return attributes; - var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, timeRate).OrderBy(h => h.BaseObject.StartTime).ToList(); + var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList(); var skills = CreateSkills(); - double sectionLength = SectionLength * timeRate; + double sectionLength = SectionLength * clockRate; // The first object doesn't generate a strain, so we begin with an incremented section end double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Difficulty foreach (Skill s in skills) s.SaveCurrentPeak(); - PopulateAttributes(attributes, beatmap, skills, timeRate); + PopulateAttributes(attributes, beatmap, skills, clockRate); return attributes; } @@ -113,16 +113,16 @@ namespace osu.Game.Rulesets.Difficulty /// The to populate with information about the difficulty of . /// The whose difficulty was processed. /// The skills which processed the difficulty. - /// The rate of time in . - protected abstract void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate); + /// The rate at which the gameplay clock is run at. + protected abstract void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double clockRate); /// /// Enumerates s to be processed from s in the . /// /// The providing the s to enumerate. - /// The rate of time in . + /// The rate at which the gameplay clock is run at. /// The enumerated s. - protected abstract IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate); + protected abstract IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate); /// /// Creates the s to calculate the difficulty of s. diff --git a/osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs index 15565ef847..a1324601aa 100644 --- a/osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/LegacyDifficultyCalculator.cs @@ -100,8 +100,8 @@ namespace osu.Game.Rulesets.Difficulty /// /// The to compute the difficulty for. /// The s that should be applied. - /// The rate of time in . + /// The rate at which the gameplay clock is run at. /// A structure containing the difficulty attributes. - protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate); + protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate); } } diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index 77c9b7e47f..23a8740f3d 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing /// public readonly HitObject LastObject; - public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate) + public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) { BaseObject = hitObject; LastObject = lastObject; - DeltaTime = (hitObject.StartTime - lastObject.StartTime) / timeRate; + DeltaTime = (hitObject.StartTime - lastObject.StartTime) / clockRate; } } } From 7ed461aa8c31a55775fde83f39d273c8b4383542 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 14:30:59 +0900 Subject: [PATCH 51/76] XMLDoc DifficultyHitObject --- .../Preprocessing/DifficultyHitObject.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index 23a8740f3d..ebbffb5143 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -5,23 +5,32 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Difficulty.Preprocessing { + /// + /// Wraps a and provides additional information to be used for difficulty calculation. + /// public class DifficultyHitObject { /// - /// Milliseconds elapsed since the of the previous . - /// - public double DeltaTime { get; private set; } - - /// - /// The this refers to. + /// The this wraps. /// public readonly HitObject BaseObject; /// - /// The previous to . + /// The last which occurs before . /// public readonly HitObject LastObject; + /// + /// Amount of time elapsed between and . + /// + public readonly double DeltaTime; + + /// + /// Creates a new . + /// + /// The which this wraps. + /// The last which occurs before in the beatmap. + /// The rate at which the gameplay clock is run at. public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) { BaseObject = hitObject; From ade5763160835ef79f0794e709dd2da820f0b16c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 14:34:02 +0900 Subject: [PATCH 52/76] Fix post-merge errors --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 6 ------ osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 9a9e72a056..6e991a1d08 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -13,10 +12,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double ApproachRate; public double OverallDifficulty; public int MaxCombo; - - public OsuDifficultyAttributes(Mod[] mods) - : base(mods) - { - } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 13d1621a39..9bbdb75343 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new Speed() }; - protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new OsuDifficultyAttributes(mods); + protected override DifficultyAttributes CreateDifficultyAttributes() => new OsuDifficultyAttributes(); protected override Mod[] DifficultyAdjustmentMods => new Mod[] { From 0609fcf7d4a2a06af0b04d91a6432aebe3008494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Feb 2019 15:52:53 +0900 Subject: [PATCH 53/76] Fix TestCaseMultiScreen intermittent failures --- osu.Game.Tests/Visual/TestCaseMultiScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs index fc4037f58b..8faaefb0bd 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual Multiplayer multi = new Multiplayer(); AddStep(@"show", () => LoadScreen(multi)); - AddWaitStep(5); + AddUntilStep(() => multi.IsCurrentScreen(), "wait until current"); AddStep(@"exit", multi.Exit); } } From bf1782636368d6428d0c9b7219fd22dcc858bd34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 16:30:29 +0900 Subject: [PATCH 54/76] Fix post-merge errors --- .../Difficulty/ManiaDifficultyAttributes.cs | 6 ------ .../Difficulty/ManiaDifficultyCalculator.cs | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 4aa6cd730d..3ff665d2c8 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -2,17 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaDifficultyAttributes : DifficultyAttributes { public double GreatHitWindow; - - public ManiaDifficultyAttributes(Mod[] mods) - : base(mods) - { - } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 523ac46515..1e7e16329a 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty return skills.ToArray(); } - protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new ManiaDifficultyAttributes(mods); + protected override DifficultyAttributes CreateDifficultyAttributes() => new ManiaDifficultyAttributes(); protected override Mod[] DifficultyAdjustmentMods { From 7ba0d090fc2a1491050c05c48219add0ad0306a8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 16:40:52 +0900 Subject: [PATCH 55/76] Fix post-merge errors --- .../Difficulty/TaikoDifficultyAttributes.cs | 6 ------ .../Difficulty/TaikoDifficultyCalculator.cs | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 07721e2ac5..75d3807bba 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public double GreatHitWindow; public int MaxCombo; - - public TaikoDifficultyAttributes(Mod[] mods) - : base(mods) - { - } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 1cdb3495ae..e2c0ea7f46 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override Skill[] CreateSkills() => new Skill[] { new Strain() }; - protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new TaikoDifficultyAttributes(mods); + protected override DifficultyAttributes CreateDifficultyAttributes() => new TaikoDifficultyAttributes(); protected override Mod[] DifficultyAdjustmentMods => new Mod[] { From 3abb281ad570ceebdd58a5d53457e031f24fd239 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 16:41:53 +0900 Subject: [PATCH 56/76] Fix post-merge errors --- .../Difficulty/CatchDifficultyAttributes.cs | 6 ------ .../Difficulty/CatchDifficultyCalculator.cs | 3 +-- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 8669543841..75f5b18607 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public double ApproachRate; public int MaxCombo; - - public CatchDifficultyAttributes(Mod[] mods) - : base(mods) - { - } } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index d4110ab1e1..f8ca066a4f 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -78,6 +77,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty new Movement(), }; - protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new CatchDifficultyAttributes(mods); + protected override DifficultyAttributes CreateDifficultyAttributes() => new CatchDifficultyAttributes(); } } From 618455f7ba3496591bc8ea2e8859e7ae74de3328 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Feb 2019 16:47:59 +0900 Subject: [PATCH 57/76] Remove exit step (needs login to show properly) --- osu.Game.Tests/Visual/TestCaseMultiScreen.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs index 8faaefb0bd..ff95d5836d 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs @@ -26,8 +26,6 @@ namespace osu.Game.Tests.Visual Multiplayer multi = new Multiplayer(); AddStep(@"show", () => LoadScreen(multi)); - AddUntilStep(() => multi.IsCurrentScreen(), "wait until current"); - AddStep(@"exit", multi.Exit); } } } From 4504aee089f3a2dd6400ef98aaf3d8705196a353 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Feb 2019 16:50:56 +0900 Subject: [PATCH 58/76] Unnecessary using --- osu.Game.Tests/Visual/TestCaseMultiScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs index ff95d5836d..804e3c5b1f 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osu.Framework.Screens; using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; From ca8b7f24b46f69c92478408f42a9a549a86d0e2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:36:33 +0900 Subject: [PATCH 59/76] Remove PopulateAttributes() --- .../Difficulty/DifficultyCalculator.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index ecfca9c589..c6e54c52e7 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -25,14 +25,12 @@ namespace osu.Game.Rulesets.Difficulty protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { - var attributes = CreateDifficultyAttributes(); - attributes.Mods = mods; + var skills = CreateSkills(); if (!beatmap.HitObjects.Any()) - return attributes; + return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList(); - var skills = CreateSkills(); double sectionLength = SectionLength * clockRate; @@ -60,9 +58,7 @@ namespace osu.Game.Rulesets.Difficulty foreach (Skill s in skills) s.SaveCurrentPeak(); - PopulateAttributes(attributes, beatmap, skills, clockRate); - - return attributes; + return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); } /// @@ -108,13 +104,13 @@ namespace osu.Game.Rulesets.Difficulty protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); /// - /// Populates after difficulty has been processed. + /// Creates to describe beatmap's calculated difficulty. /// - /// The to populate with information about the difficulty of . /// The whose difficulty was processed. + /// The s that were applied during the process. /// The skills which processed the difficulty. /// The rate at which the gameplay clock is run at. - protected abstract void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double clockRate); + protected abstract DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate); /// /// Enumerates s to be processed from s in the . @@ -129,11 +125,5 @@ namespace osu.Game.Rulesets.Difficulty /// /// The s. protected abstract Skill[] CreateSkills(); - - /// - /// Creates an empty . - /// - /// The empty . - protected abstract DifficultyAttributes CreateDifficultyAttributes(); } } From 847f7d8658f1124dbac22b7c2d778538cb200b95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:38:33 +0900 Subject: [PATCH 60/76] Adjust with PopulateAttributes() removal --- .../Difficulty/OsuDifficultyCalculator.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 9bbdb75343..70ee7e251d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,28 +25,32 @@ namespace osu.Game.Rulesets.Osu.Difficulty { } - protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - var osuAttributes = (OsuDifficultyAttributes)attributes; + if (beatmap.HitObjects.Count == 0) + return new OsuDifficultyAttributes(); double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future - double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; - double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; + double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate; + double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); - osuAttributes.StarRating = starRating; - osuAttributes.AimStrain = aimRating; - osuAttributes.SpeedStrain = speedRating; - osuAttributes.ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5; - osuAttributes.OverallDifficulty = (80 - hitWindowGreat) / 6; - osuAttributes.MaxCombo = maxCombo; + return new OsuDifficultyAttributes + { + StarRating = starRating, + AimStrain = aimRating, + SpeedStrain = speedRating, + ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, + OverallDifficulty = (80 - hitWindowGreat) / 6, + MaxCombo = maxCombo + }; } protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) @@ -69,8 +73,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty new Speed() }; - protected override DifficultyAttributes CreateDifficultyAttributes() => new OsuDifficultyAttributes(); - protected override Mod[] DifficultyAdjustmentMods => new Mod[] { new OsuModDoubleTime(), From 37f9ac6eca7b5816c0210f93ee7ef2ce40a1bc78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:39:30 +0900 Subject: [PATCH 61/76] Populate mods too --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 70ee7e251d..63d172d22b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new OsuDifficultyAttributes { StarRating = starRating, + Mods = mods, AimStrain = aimRating, SpeedStrain = speedRating, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, From f19a52b960c19f767ea134a8c5cf9b80901c46c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:40:35 +0900 Subject: [PATCH 62/76] Rename argument --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 63d172d22b..b146d201cc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty }; } - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { // The first jump is formed by the first two hitobjects of the map. // If the map has less than two OsuHitObjects, the enumerator will not return anything. @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty var last = beatmap.HitObjects[i - 1]; var current = beatmap.HitObjects[i]; - yield return new OsuDifficultyHitObject(current, lastLast, last, timeRate); + yield return new OsuDifficultyHitObject(current, lastLast, last, clockRate); } } From 2765ffa19093b796b38e8bf0efe3bcd2d7ebb486 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:42:24 +0900 Subject: [PATCH 63/76] Update with PopulateAttributes() removal --- .../Difficulty/CatchDifficultyCalculator.cs | 25 +++++++++++-------- .../Preprocessing/CatchDifficultyHitObject.cs | 4 +-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index f8ca066a4f..d29dd4591e 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -30,19 +31,23 @@ namespace osu.Game.Rulesets.Catch.Difficulty halfCatchWidth = catcher.CatchWidth * 0.5f; } - protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - var catchAttributes = (CatchDifficultyAttributes)attributes; + if (beatmap.HitObjects.Count == 0) + return new CatchDifficultyAttributes(); // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; + double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; - catchAttributes.StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor; - catchAttributes.ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0; - catchAttributes.MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)); + return new CatchDifficultyAttributes + { + StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor, + ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, + MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)) + }; } - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { CatchHitObject lastObject = null; @@ -58,13 +63,13 @@ namespace osu.Game.Rulesets.Catch.Difficulty { // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. case Fruit fruit: - yield return new CatchDifficultyHitObject(fruit, lastObject, timeRate, halfCatchWidth); + yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth); lastObject = hitObject; break; case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) { - yield return new CatchDifficultyHitObject(nested, lastObject, timeRate, halfCatchWidth); + yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth); lastObject = nested; } break; @@ -76,7 +81,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty { new Movement(), }; - - protected override DifficultyAttributes CreateDifficultyAttributes() => new CatchDifficultyAttributes(); } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 6ce45bbbde..6d00bb27b5 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -24,8 +24,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing /// public readonly double StrainTime; - public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate, float halfCatcherWidth) - : base(hitObject, lastObject, timeRate) + public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) + : base(hitObject, lastObject, clockRate) { // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; From 8459cf6ed0f28c08e0da4aebf8ce26d63c284bd6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:43:12 +0900 Subject: [PATCH 64/76] Missed argument --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 31e69de6ab..930c711783 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; - public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double timeRate) - : base(hitObject, lastObject, timeRate) + public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate) + : base(hitObject, lastObject, clockRate) { this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; From 0ef15f5bd5156b04cd972e553ebd64c9f6b7ca67 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:45:16 +0900 Subject: [PATCH 65/76] Update with PopulateAttributes() removal --- .../Preprocessing/TaikoDifficultyHitObject.cs | 4 ++-- .../Difficulty/TaikoDifficultyCalculator.cs | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs index 4d63d81663..24345275c1 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing { public readonly bool HasTypeChange; - public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate) - : base(hitObject, lastObject, timeRate) + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) + : base(hitObject, lastObject, clockRate) { HasTypeChange = lastObject is RimHit != hitObject is RimHit; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index e2c0ea7f46..d973b80e12 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -24,27 +24,29 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { } - protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - var taikoAttributes = (TaikoDifficultyAttributes)attributes; + if (beatmap.HitObjects.Count == 0) + return new TaikoDifficultyAttributes(); - taikoAttributes.StarRating = skills.Single().DifficultyValue() * star_scaling_factor; - - // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - taikoAttributes.GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; - taikoAttributes.MaxCombo = beatmap.HitObjects.Count(h => h is Hit); + return new TaikoDifficultyAttributes + { + StarRating = skills.Single().DifficultyValue() * star_scaling_factor, + Mods = mods, + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future + GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate, + MaxCombo = beatmap.HitObjects.Count(h => h is Hit), + }; } - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { for (int i = 1; i < beatmap.HitObjects.Count; i++) - yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], timeRate); + yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } protected override Skill[] CreateSkills() => new Skill[] { new Strain() }; - protected override DifficultyAttributes CreateDifficultyAttributes() => new TaikoDifficultyAttributes(); - protected override Mod[] DifficultyAdjustmentMods => new Mod[] { new TaikoModDoubleTime(), From 1a645b511592f6a1b275b69d24e874c4c939369b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:45:52 +0900 Subject: [PATCH 66/76] Fix mods not being populated --- .../Difficulty/CatchDifficultyCalculator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index d29dd4591e..9e6511b362 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new CatchDifficultyAttributes(); + return new CatchDifficultyAttributes { Mods = mods }; // this is the same as osu!, so there's potential to share the implementation... maybe double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return new CatchDifficultyAttributes { StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor, + Mods = mods, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)) }; From 21f9c813b20f48d0819d36f16c3963eec1a8cccb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:46:18 +0900 Subject: [PATCH 67/76] Fix mods not being populated --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b146d201cc..3a0467a0c8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new OsuDifficultyAttributes(); + return new OsuDifficultyAttributes { Mods = mods }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; From c264a9cc74ad9b64026793a2c7ab9a7f1726ed84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:46:40 +0900 Subject: [PATCH 68/76] Fix mods not being populated --- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index d973b80e12..bed0fd9479 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new TaikoDifficultyAttributes(); + return new TaikoDifficultyAttributes { Mods = mods }; return new TaikoDifficultyAttributes { From 5457097342ee87769bdb3fcf4557e482213b4765 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:48:00 +0900 Subject: [PATCH 69/76] Update with PopulateAttributes() removal --- .../Difficulty/ManiaDifficultyCalculator.cs | 22 ++++++++++--------- .../Preprocessing/ManiaDifficultyHitObject.cs | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 1e7e16329a..548a5d1bca 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -29,14 +29,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); } - protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - var maniaAttributes = (ManiaDifficultyAttributes)attributes; + if (beatmap.HitObjects.Count == 0) + return new ManiaDifficultyAttributes { Mods = mods }; - maniaAttributes.StarRating = difficultyValue(skills) * star_scaling_factor; - - // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future - maniaAttributes.GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; + return new ManiaDifficultyAttributes + { + StarRating = difficultyValue(skills) * star_scaling_factor, + Mods = mods, + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future + GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate, + }; } private double difficultyValue(Skill[] skills) @@ -71,12 +75,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty return difficulty; } - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { columnCount = ((ManiaBeatmap)beatmap).TotalColumns; for (int i = 1; i < beatmap.HitObjects.Count; i++) - yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], timeRate); + yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } protected override Skill[] CreateSkills() @@ -89,8 +93,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty return skills.ToArray(); } - protected override DifficultyAttributes CreateDifficultyAttributes() => new ManiaDifficultyAttributes(); - protected override Mod[] DifficultyAdjustmentMods { get diff --git a/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs index aa823e7586..29ba934e9f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Preprocessing { public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject; - public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate) - : base(hitObject, lastObject, timeRate) + public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) + : base(hitObject, lastObject, clockRate) { } } From 4dcf39846de896ebff28d5ae5536c99bf13fdaa9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:52:59 +0900 Subject: [PATCH 70/76] Pass beatmap to CreateSkills() --- .../Rulesets/Difficulty/DifficultyCalculator.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index c6e54c52e7..29ec9aae25 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Difficulty protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { - var skills = CreateSkills(); + var skills = CreateSkills(beatmap); if (!beatmap.HitObjects.Any()) return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); @@ -106,9 +106,9 @@ namespace osu.Game.Rulesets.Difficulty /// /// Creates to describe beatmap's calculated difficulty. /// - /// The whose difficulty was processed. - /// The s that were applied during the process. - /// The skills which processed the difficulty. + /// The whose difficulty was calculated. + /// The s that difficulty was calculated with. + /// The skills which processed the beatmap. /// The rate at which the gameplay clock is run at. protected abstract DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate); @@ -121,9 +121,10 @@ namespace osu.Game.Rulesets.Difficulty protected abstract IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate); /// - /// Creates the s to calculate the difficulty of s. + /// Creates the s to calculate the difficulty of an . /// + /// The whose difficulty will be calculated.The s. - protected abstract Skill[] CreateSkills(); + protected abstract Skill[] CreateSkills(IBeatmap beatmap); } } From ea281e8596e51028a6e24b71cd5feca905dd5a57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:54:00 +0900 Subject: [PATCH 71/76] Add beatmap argument --- osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 9e6511b362..89f3a8c25e 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty } } - protected override Skill[] CreateSkills() => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Movement(), }; From 4efc03cdf0ab1e401f85d192ded9124e8e365d14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:56:38 +0900 Subject: [PATCH 72/76] Add beatmap argument + fix crashes --- .../Difficulty/ManiaDifficultyCalculator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 548a5d1bca..4790bde9ee 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty { private const double star_scaling_factor = 0.018; - private int columnCount; - private readonly bool isForCurrentRuleset; public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) @@ -77,14 +75,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - columnCount = ((ManiaBeatmap)beatmap).TotalColumns; for (int i = 1; i < beatmap.HitObjects.Count; i++) yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } - protected override Skill[] CreateSkills() + protected override Skill[] CreateSkills(IBeatmap beatmap) { + int columnCount = ((ManiaBeatmap)beatmap).TotalColumns; + var skills = new List { new Overall(columnCount) }; for (int i = 0; i < columnCount; i++) From 5ff890434cdd2da760087b60629006bf3bec127b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:57:29 +0900 Subject: [PATCH 73/76] Add beatmap argument --- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index bed0fd9479..685ad9949b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } - protected override Skill[] CreateSkills() => new Skill[] { new Strain() }; + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] { From 03802930989a83dfdbbf3745757e0bab2fcf4e44 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:58:02 +0900 Subject: [PATCH 74/76] Add beatmap argument --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 3a0467a0c8..e2a1542574 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } } - protected override Skill[] CreateSkills() => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Aim(), new Speed() From d6a2fe6891173c82c9309d5a56962c3f6707b392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Feb 2019 10:29:08 +0900 Subject: [PATCH 75/76] Remove excess newline --- osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 4790bde9ee..59fed1031f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -75,7 +75,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - for (int i = 1; i < beatmap.HitObjects.Count; i++) yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } From 2c76a039ca3ebab69ef0bc199e0692e1bb9cf8f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Feb 2019 10:46:25 +0900 Subject: [PATCH 76/76] Remove unnecessary folder reference --- osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 563ed2e7ca..656ebcc7c2 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -10,7 +10,4 @@ - - - - \ No newline at end of file +