From 19789840066ccc497907fd73be46b8a0c01782a9 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sun, 12 Feb 2017 17:49:30 +0100 Subject: [PATCH 01/15] mode selector line has the correct length now --- osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 0ef56435e6..c8ac745bba 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -31,6 +31,10 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Y; + float length = Enum.GetValues(typeof(PlayMode)).Length * ToolbarButton.WIDTH + padding*2; + float lineLength = (padding * 2 + ToolbarButton.WIDTH) / length; + + Children = new Drawable[] { new OpaqueBackground(), @@ -41,12 +45,12 @@ namespace osu.Game.Overlays.Toolbar Direction = FlowDirection.HorizontalOnly, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = 10, Right = 10 }, + Padding = new MarginPadding { Left = padding, Right = padding }, }, modeButtonLine = new Container { RelativeSizeAxes = Axes.X, - Size = new Vector2(0.3f, 3), + Size = new Vector2(lineLength, 3), Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, Masking = true, From c1ba53fa093988263b7df91927e7ccf54b95a988 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Mon, 13 Feb 2017 16:56:15 +0100 Subject: [PATCH 02/15] calculation is now a 'one-liner' --- osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index c8ac745bba..69e3403e4e 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -31,8 +31,7 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Y; - float length = Enum.GetValues(typeof(PlayMode)).Length * ToolbarButton.WIDTH + padding*2; - float lineLength = (padding * 2 + ToolbarButton.WIDTH) / length; + float lineLength = (padding * 2 + ToolbarButton.WIDTH) / (Enum.GetValues(typeof(PlayMode)).Length * ToolbarButton.WIDTH + padding * 2); Children = new Drawable[] From 1b08f6aca4d68f1c1e9c195545ec4e163165364a Mon Sep 17 00:00:00 2001 From: Jorolf Date: Tue, 14 Feb 2017 14:24:54 +0100 Subject: [PATCH 03/15] Line length is absolute now --- osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 69e3403e4e..c242460cb0 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Y; - float lineLength = (padding * 2 + ToolbarButton.WIDTH) / (Enum.GetValues(typeof(PlayMode)).Length * ToolbarButton.WIDTH + padding * 2); + float lineLength = padding * 2 + ToolbarButton.WIDTH; Children = new Drawable[] @@ -48,7 +48,6 @@ namespace osu.Game.Overlays.Toolbar }, modeButtonLine = new Container { - RelativeSizeAxes = Axes.X, Size = new Vector2(lineLength, 3), Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, From e717daa504b037347299626a7ef4b352b7244bbe Mon Sep 17 00:00:00 2001 From: Jorolf Date: Tue, 14 Feb 2017 16:05:57 +0100 Subject: [PATCH 04/15] Line length is calculated inline --- osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index c242460cb0..a63fcce2dc 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -31,9 +31,6 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Y; - float lineLength = padding * 2 + ToolbarButton.WIDTH; - - Children = new Drawable[] { new OpaqueBackground(), @@ -48,7 +45,7 @@ namespace osu.Game.Overlays.Toolbar }, modeButtonLine = new Container { - Size = new Vector2(lineLength, 3), + Size = new Vector2(padding * 2 + ToolbarButton.WIDTH, 3), Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, Masking = true, From a48e4a31a79615928aba74c2d7eb85d43c371d40 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 18 Feb 2017 21:34:21 +0100 Subject: [PATCH 05/15] Parallax Option works now --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- .../Graphics/Containers/ParallaxContainer.cs | 28 ++++++++++++++++--- osu.Game/Screens/Menu/MainMenu.cs | 1 - 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 381bca2a71..1562571efc 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -31,6 +31,7 @@ namespace osu.Game.Configuration Set(OsuConfig.SnakingInSliders, true); Set(OsuConfig.SnakingOutSliders, false); + Set(OsuConfig.MenuParallax, true); //todo: implement all settings below this line (remove the Disabled set when doing so). Set(OsuConfig.MouseSpeed, 1.0).Disabled = true; @@ -143,7 +144,6 @@ namespace osu.Game.Configuration Set(OsuConfig.DetectPerformanceIssues, true).Disabled = true; Set(OsuConfig.MenuMusic, true).Disabled = true; Set(OsuConfig.MenuVoice, true).Disabled = true; - Set(OsuConfig.MenuParallax, true).Disabled = true; Set(OsuConfig.RawInput, false).Disabled = true; Set(OsuConfig.AbsoluteToOsuWindow, Get(OsuConfig.RawInput)).Disabled = true; Set(OsuConfig.ShowMenuTips, true).Disabled = true; diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index fe3601a5f2..7513961574 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -8,12 +8,26 @@ using OpenTK; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Transformations; +using osu.Game.Configuration; namespace osu.Game.Graphics.Containers { class ParallaxContainer : Container { - public float ParallaxAmount = 0.02f; + public float ParallaxAmount + { + get + { + return defaultParallaxAmount; + } + + set + { + defaultParallaxAmount = value; + } + } + private float parallaxAmount; + private float defaultParallaxAmount = 0.02f; public override bool Contains(Vector2 screenSpacePos) => true; @@ -34,9 +48,15 @@ namespace osu.Game.Graphics.Containers protected override Container Content => content; [BackgroundDependencyLoader] - private void load(UserInputManager input) + private void load(UserInputManager input, OsuConfigManager config) { this.input = input; + + config.GetBindable(OsuConfig.MenuParallax).ValueChanged += delegate + { + parallaxAmount = config.GetBindable(OsuConfig.MenuParallax) ? defaultParallaxAmount : 0; + }; + parallaxAmount = config.GetBindable(OsuConfig.MenuParallax) ? defaultParallaxAmount : 0; } bool firstUpdate = true; @@ -46,8 +66,8 @@ namespace osu.Game.Graphics.Containers base.Update(); Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; - content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); - content.Scale = new Vector2(1 + ParallaxAmount); + content.MoveTo(offset * parallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); + content.Scale = new Vector2(1 + parallaxAmount); firstUpdate = false; } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 29fc44d673..1a6310ea4e 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -59,7 +59,6 @@ namespace osu.Game.Screens.Menu background.Preload(game); buttons.OnSettings = game.ToggleOptions; - } protected override void OnEntering(Screen last) From 77bfe57d8cd6c49064162234f221b8dcc869d488 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 18 Feb 2017 22:00:07 +0100 Subject: [PATCH 06/15] compacted code, made public bool to disable it --- .../Graphics/Containers/ParallaxContainer.cs | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 7513961574..328d74994e 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -14,20 +14,8 @@ namespace osu.Game.Graphics.Containers { class ParallaxContainer : Container { - public float ParallaxAmount - { - get - { - return defaultParallaxAmount; - } - - set - { - defaultParallaxAmount = value; - } - } - private float parallaxAmount; - private float defaultParallaxAmount = 0.02f; + public float ParallaxAmount = 0.02f; + public bool ParallaxEnabled = true; public override bool Contains(Vector2 screenSpacePos) => true; @@ -54,9 +42,13 @@ namespace osu.Game.Graphics.Containers config.GetBindable(OsuConfig.MenuParallax).ValueChanged += delegate { - parallaxAmount = config.GetBindable(OsuConfig.MenuParallax) ? defaultParallaxAmount : 0; + ParallaxEnabled = config.GetBindable(OsuConfig.MenuParallax); + if (!ParallaxEnabled) + { + content.MoveTo(Vector2.Zero, 1000, EasingTypes.OutQuint); + content.Scale = Vector2.One; + } }; - parallaxAmount = config.GetBindable(OsuConfig.MenuParallax) ? defaultParallaxAmount : 0; } bool firstUpdate = true; @@ -65,9 +57,11 @@ namespace osu.Game.Graphics.Containers { base.Update(); - Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; - content.MoveTo(offset * parallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); - content.Scale = new Vector2(1 + parallaxAmount); + if (ParallaxEnabled) { + Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; + content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); + content.Scale = new Vector2(1 + ParallaxAmount); + } firstUpdate = false; } From f166bb0f96b28e0b269326aae4cf4c7888bd97b6 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 18 Feb 2017 22:26:48 +0100 Subject: [PATCH 07/15] Moved movement to the default position to property --- .../Graphics/Containers/ParallaxContainer.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 328d74994e..3cf2e0f413 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -15,7 +15,24 @@ namespace osu.Game.Graphics.Containers class ParallaxContainer : Container { public float ParallaxAmount = 0.02f; - public bool ParallaxEnabled = true; + + private bool parallaxEnabled = true; + public bool ParallaxEnabled + { + get + { + return parallaxEnabled; + } + set + { + parallaxEnabled = value; + if (!parallaxEnabled) + { + content.MoveTo(Vector2.Zero, 1000, EasingTypes.OutQuint); + content.Scale = Vector2.One; + } + } + } public override bool Contains(Vector2 screenSpacePos) => true; @@ -43,11 +60,6 @@ namespace osu.Game.Graphics.Containers config.GetBindable(OsuConfig.MenuParallax).ValueChanged += delegate { ParallaxEnabled = config.GetBindable(OsuConfig.MenuParallax); - if (!ParallaxEnabled) - { - content.MoveTo(Vector2.Zero, 1000, EasingTypes.OutQuint); - content.Scale = Vector2.One; - } }; } From e83ac8b04c6f50a256819a8c2698dab77933781e Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sun, 19 Feb 2017 13:47:26 +0100 Subject: [PATCH 08/15] Removed public ParallaxEnabled property --- .../Graphics/Containers/ParallaxContainer.cs | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 3cf2e0f413..7696e82009 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -17,22 +17,6 @@ namespace osu.Game.Graphics.Containers public float ParallaxAmount = 0.02f; private bool parallaxEnabled = true; - public bool ParallaxEnabled - { - get - { - return parallaxEnabled; - } - set - { - parallaxEnabled = value; - if (!parallaxEnabled) - { - content.MoveTo(Vector2.Zero, 1000, EasingTypes.OutQuint); - content.Scale = Vector2.One; - } - } - } public override bool Contains(Vector2 screenSpacePos) => true; @@ -59,7 +43,12 @@ namespace osu.Game.Graphics.Containers config.GetBindable(OsuConfig.MenuParallax).ValueChanged += delegate { - ParallaxEnabled = config.GetBindable(OsuConfig.MenuParallax); + parallaxEnabled = config.GetBindable(OsuConfig.MenuParallax); + if (!parallaxEnabled) + { + content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); + content.Scale = Vector2.One; + } }; } @@ -69,7 +58,7 @@ namespace osu.Game.Graphics.Containers { base.Update(); - if (ParallaxEnabled) { + if (parallaxEnabled) { Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); content.Scale = new Vector2(1 + ParallaxAmount); From 7f751d365338b7bde8fd733bbf632a2ac4ee281a Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sun, 19 Feb 2017 16:54:00 +0100 Subject: [PATCH 09/15] Update ParallaxContainer.cs --- osu.Game/Graphics/Containers/ParallaxContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 7696e82009..9208c5008a 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -58,7 +58,8 @@ namespace osu.Game.Graphics.Containers { base.Update(); - if (parallaxEnabled) { + if (parallaxEnabled) + { Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); content.Scale = new Vector2(1 + ParallaxAmount); From 1e0a694ff86dc9e942d2adc125b20621e041fb3d Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sun, 19 Feb 2017 17:11:31 +0100 Subject: [PATCH 10/15] replaced bool with Bindable Also accounted for the ParallaxAmount when moving to default position --- osu.Game/Graphics/Containers/ParallaxContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 9208c5008a..bc54fd8fdc 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -9,6 +9,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Transformations; using osu.Game.Configuration; +using osu.Framework.Configuration; namespace osu.Game.Graphics.Containers { @@ -16,7 +17,7 @@ namespace osu.Game.Graphics.Containers { public float ParallaxAmount = 0.02f; - private bool parallaxEnabled = true; + private Bindable parallaxEnabled; public override bool Contains(Vector2 screenSpacePos) => true; @@ -40,14 +41,13 @@ namespace osu.Game.Graphics.Containers private void load(UserInputManager input, OsuConfigManager config) { this.input = input; - - config.GetBindable(OsuConfig.MenuParallax).ValueChanged += delegate + parallaxEnabled = config.GetBindable(OsuConfig.MenuParallax); + parallaxEnabled.ValueChanged += delegate { - parallaxEnabled = config.GetBindable(OsuConfig.MenuParallax); if (!parallaxEnabled) { content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); - content.Scale = Vector2.One; + content.Scale = new Vector2(1 + ParallaxAmount); } }; } From 417f14638646e47dbbe7a99a99a0af4f2454393e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 19 Feb 2017 17:41:51 +0100 Subject: [PATCH 11/15] Add difficulty calculation Adds base classes for difficulty calculations, hooks them up with carousel container, and adds a port of the osu difficulty calculator. --- osu-framework | 2 +- .../CatchDifficultyCalculator.cs | 27 +++ osu.Game.Modes.Catch/CatchRuleset.cs | 5 +- .../osu.Game.Modes.Catch.csproj | 1 + .../ManiaDifficultyCalculator.cs | 30 +++ osu.Game.Modes.Mania/ManiaRuleset.cs | 6 +- .../osu.Game.Modes.Mania.csproj | 1 + .../Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Modes.Osu/Objects/HitCircle.cs | 3 + osu.Game.Modes.Osu/Objects/OsuHitObject.cs | 16 +- .../Objects/OsuHitObjectDifficulty.cs | 200 ++++++++++++++++++ osu.Game.Modes.Osu/Objects/Slider.cs | 2 + osu.Game.Modes.Osu/Objects/SliderTick.cs | 2 + osu.Game.Modes.Osu/Objects/Spinner.cs | 2 + osu.Game.Modes.Osu/OsuDifficultyCalculator.cs | 193 +++++++++++++++++ osu.Game.Modes.Osu/OsuRuleset.cs | 2 + osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj | 2 + .../TaikoDifficultyCalculator.cs | 27 +++ osu.Game.Modes.Taiko/TaikoRuleset.cs | 4 +- .../osu.Game.Modes.Taiko.csproj | 1 + .../Beatmaps/Formats/OsuLegacyDecoderTest.cs | 3 - osu.Game/Beatmaps/DifficultyCalculator.cs | 50 +++++ osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 4 +- osu.Game/Beatmaps/Timing/TimingChange.cs | 6 - osu.Game/Beatmaps/WorkingBeatmap.cs | 1 - osu.Game/Database/BeatmapInfo.cs | 19 +- osu.Game/Modes/Ruleset.cs | 2 + osu.Game/Screens/Select/PlaySongSelect.cs | 4 +- osu.Game/osu.Game.csproj | 1 + 29 files changed, 587 insertions(+), 31 deletions(-) create mode 100644 osu.Game.Modes.Catch/CatchDifficultyCalculator.cs create mode 100644 osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs create mode 100644 osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs create mode 100644 osu.Game.Modes.Osu/OsuDifficultyCalculator.cs create mode 100644 osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs create mode 100644 osu.Game/Beatmaps/DifficultyCalculator.cs diff --git a/osu-framework b/osu-framework index 697d8b7e95..f370d4d6ef 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 697d8b7e9530ba7914d9c78012329ce1559e3681 +Subproject commit f370d4d6ef9f975932324d30272dfffc929a6622 diff --git a/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs b/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs new file mode 100644 index 0000000000..47fe1774bd --- /dev/null +++ b/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Modes.Catch.Objects; +using osu.Game.Modes.Objects; +using System; +using System.Collections.Generic; + +namespace osu.Game.Modes.Catch +{ + public class CatchDifficultyCalculator : DifficultyCalculator + { + protected override PlayMode PlayMode => PlayMode.Catch; + + public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap) + { + } + + protected override HitObjectConverter Converter => new CatchConverter(); + + protected override double ComputeDifficulty(Dictionary categoryDifficulty) + { + return 0; + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Catch/CatchRuleset.cs b/osu.Game.Modes.Catch/CatchRuleset.cs index 5db11737f4..48a84617da 100644 --- a/osu.Game.Modes.Catch/CatchRuleset.cs +++ b/osu.Game.Modes.Catch/CatchRuleset.cs @@ -1,14 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using osu.Game.Graphics; using osu.Game.Modes.Catch.UI; using osu.Game.Modes.Objects; -using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.UI; using osu.Game.Modes.UI; using osu.Game.Beatmaps; +using System; namespace osu.Game.Modes.Catch { @@ -25,5 +24,7 @@ namespace osu.Game.Modes.Catch public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap); } } diff --git a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj index 8d32c23b9b..5e3f0f1d96 100644 --- a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj +++ b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj @@ -47,6 +47,7 @@ + diff --git a/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs new file mode 100644 index 0000000000..5075c44db6 --- /dev/null +++ b/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Modes.Mania.Objects; +using osu.Game.Modes.Objects; +using System; +using System.Collections.Generic; + +namespace osu.Game.Modes.Mania +{ + public class ManiaDifficultyCalculator : DifficultyCalculator + { + protected override PlayMode PlayMode => PlayMode.Mania; + + private int columns; + + public ManiaDifficultyCalculator(Beatmap beatmap, int columns = 5) : base(beatmap) + { + this.columns = columns; + } + + protected override HitObjectConverter Converter => new ManiaConverter(columns); + + protected override double ComputeDifficulty(Dictionary categoryDifficulty) + { + return 0; + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Mania/ManiaRuleset.cs b/osu.Game.Modes.Mania/ManiaRuleset.cs index 654a4ddaa6..28c298be95 100644 --- a/osu.Game.Modes.Mania/ManiaRuleset.cs +++ b/osu.Game.Modes.Mania/ManiaRuleset.cs @@ -1,15 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using osu.Game.Graphics; using osu.Game.Modes.Mania.UI; using osu.Game.Modes.Objects; -using osu.Game.Modes.Osu; -using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.UI; using osu.Game.Modes.UI; using osu.Game.Beatmaps; +using System; namespace osu.Game.Modes.Mania { @@ -26,5 +24,7 @@ namespace osu.Game.Modes.Mania public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap); } } diff --git a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj index 7ff501c86d..ecd3fe6423 100644 --- a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj +++ b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj @@ -47,6 +47,7 @@ + diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs index cd72483d1e..2f6322a046 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs @@ -108,7 +108,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables } } - private Vector2 scaleToCircle => new Vector2(circle.Scale * circle.DrawWidth / DrawWidth) * 0.95f; + private Vector2 scaleToCircle => (circle.Scale * circle.DrawWidth / DrawWidth) * 0.95f; private float spinsPerMinuteNeeded = 100 + (5 * 15); //TODO: read per-map OD and place it on the 5 diff --git a/osu.Game.Modes.Osu/Objects/HitCircle.cs b/osu.Game.Modes.Osu/Objects/HitCircle.cs index aa45ac7fb9..0eaf5abdff 100644 --- a/osu.Game.Modes.Osu/Objects/HitCircle.cs +++ b/osu.Game.Modes.Osu/Objects/HitCircle.cs @@ -1,9 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; + namespace osu.Game.Modes.Osu.Objects { public class HitCircle : OsuHitObject { + public override HitObjectType Type => HitObjectType.Circle; } } diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObject.cs b/osu.Game.Modes.Osu/Objects/OsuHitObject.cs index c23c9a0b64..f454ebc470 100644 --- a/osu.Game.Modes.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Modes.Osu/Objects/OsuHitObject.cs @@ -25,6 +25,8 @@ namespace osu.Game.Modes.Osu.Objects public float Scale { get; set; } = 1; + public abstract HitObjectType Type { get; } + public override void SetDefaultsFromBeatmap(Beatmap beatmap) { base.SetDefaultsFromBeatmap(beatmap); @@ -36,14 +38,12 @@ namespace osu.Game.Modes.Osu.Objects [Flags] public enum HitObjectType { - Circle = 1, - Slider = 2, - NewCombo = 4, - CircleNewCombo = 5, - SliderNewCombo = 6, - Spinner = 8, + Circle = 1 << 0, + Slider = 1 << 1, + NewCombo = 1 << 2, + Spinner = 1 << 3, ColourHax = 122, - Hold = 128, - ManiaLong = 128, + Hold = 1 << 7, + SliderTick = 1 << 8, } } diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs b/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs new file mode 100644 index 0000000000..637c754ac4 --- /dev/null +++ b/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs @@ -0,0 +1,200 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using System; +using System.Diagnostics; +using System.Linq; + +namespace osu.Game.Modes.Osu.Objects +{ + class OsuHitObjectDifficulty + { + /// + /// Factor by how much speed / aim strain decays per second. + /// + /// + /// These values are results of tweaking a lot and taking into account general feedback. + /// Opinionated observation: Speed is easier to maintain than accurate jumps. + /// + internal static readonly double[] DECAY_BASE = { 0.3, 0.15 }; + + /// + /// Pseudo threshold values to distinguish between "singles" and "streams" + /// + /// + /// Of course the border can not be defined clearly, therefore the algorithm has a smooth transition between those values. + /// They also are based on tweaking and general feedback. + /// + private const double STREAM_SPACING_TRESHOLD = 110, + SINGLE_SPACING_TRESHOLD = 125; + + /// + /// Scaling values for weightings to keep aim and speed difficulty in balance. + /// + /// + /// Found from testing a very large map pool (containing all ranked maps) and keeping the average values the same. + /// + private static readonly double[] SPACING_WEIGHT_SCALING = { 1400, 26.25 }; + + /// + /// Almost the normed diameter of a circle (104 osu pixel). That is -after- position transforming. + /// + private const double ALMOST_DIAMETER = 90; + + internal OsuHitObject BaseHitObject; + internal double[] Strains = { 1, 1 }; + + internal int MaxCombo = 1; + + private Vector2 normalizedStartPosition; + private Vector2 normalizedEndPosition; + private float lazySliderLength; + + internal OsuHitObjectDifficulty(OsuHitObject baseHitObject) + { + BaseHitObject = baseHitObject; + float circleRadius = baseHitObject.Scale * 64; + + Slider slider = BaseHitObject as Slider; + if (slider != null) + MaxCombo += slider.Ticks.Count(); + + // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. + float scalingFactor = (52.0f / circleRadius); + if (circleRadius < 30) + { + float smallCircleBonus = Math.Min(30.0f - circleRadius, 5.0f) / 50.0f; + scalingFactor *= 1.0f + smallCircleBonus; + } + + normalizedStartPosition = BaseHitObject.StackedPosition * scalingFactor; + + lazySliderLength = 0; + + // Calculate approximation of lazy movement on the slider + if (slider != null) + { + float sliderFollowCircleRadius = circleRadius * 3; // Not sure if this is correct, but here we do not need 100% exact values. This comes pretty darn close in my tests. + + // For simplifying this step we use actual osu! coordinates and simply scale the length, that we obtain by the ScalingFactor later + Vector2 cursorPos = baseHitObject.StackedPosition; + + Action addSliderVertex = delegate (Vector2 pos) + { + Vector2 difference = pos - cursorPos; + float distance = difference.Length; + + // Did we move away too far? + if (distance > sliderFollowCircleRadius) + { + // Yep, we need to move the cursor + difference.Normalize(); // Obtain the direction of difference. We do no longer need the actual difference + distance -= sliderFollowCircleRadius; + cursorPos += difference * distance; // We move the cursor just as far as needed to stay in the follow circle + lazySliderLength += distance; + } + }; + + // Actual computation of the first lazy curve + foreach (var tick in slider.Ticks) + addSliderVertex(tick.StackedPosition); + + addSliderVertex(baseHitObject.StackedEndPosition); + + lazySliderLength *= scalingFactor; + normalizedEndPosition = cursorPos * scalingFactor; + } + // We have a normal HitCircle or a spinner + else + normalizedEndPosition = normalizedStartPosition; + } + + internal void CalculateStrains(OsuHitObjectDifficulty previousHitObject, double timeRate) + { + calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Speed, timeRate); + calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Aim, timeRate); + } + + // Caution: The subjective values are strong with this one + private static double spacingWeight(double distance, OsuDifficultyCalculator.DifficultyType type) + { + switch (type) + { + case OsuDifficultyCalculator.DifficultyType.Speed: + if (distance > SINGLE_SPACING_TRESHOLD) + return 2.5; + else if (distance > STREAM_SPACING_TRESHOLD) + return 1.6 + 0.9 * (distance - STREAM_SPACING_TRESHOLD) / (SINGLE_SPACING_TRESHOLD - STREAM_SPACING_TRESHOLD); + else if (distance > ALMOST_DIAMETER) + return 1.2 + 0.4 * (distance - ALMOST_DIAMETER) / (STREAM_SPACING_TRESHOLD - ALMOST_DIAMETER); + else if (distance > ALMOST_DIAMETER / 2) + return 0.95 + 0.25 * (distance - (ALMOST_DIAMETER / 2)) / (ALMOST_DIAMETER / 2); + else + return 0.95; + + case OsuDifficultyCalculator.DifficultyType.Aim: + return Math.Pow(distance, 0.99); + } + + Debug.Assert(false, "Invalid osu difficulty hit object type."); + return 0; + } + + private void calculateSpecificStrain(OsuHitObjectDifficulty previousHitObject, OsuDifficultyCalculator.DifficultyType type, double timeRate) + { + double addition = 0; + double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; + double decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000); + + if (BaseHitObject.Type == HitObjectType.Spinner) + { + // Do nothing for spinners + } + else if (BaseHitObject.Type == HitObjectType.Slider) + { + switch (type) + { + case OsuDifficultyCalculator.DifficultyType.Speed: + + // For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast. + // The spacing weight exists to differentiate between being able to easily alternate or having to single. + addition = + spacingWeight(previousHitObject.lazySliderLength + + DistanceTo(previousHitObject), type) * + SPACING_WEIGHT_SCALING[(int)type]; + + break; + case OsuDifficultyCalculator.DifficultyType.Aim: + + // For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference + // to multiple jumps. + addition = + ( + spacingWeight(previousHitObject.lazySliderLength, type) + + spacingWeight(DistanceTo(previousHitObject), type) + ) * + SPACING_WEIGHT_SCALING[(int)type]; + + break; + } + } + else if (BaseHitObject.Type == HitObjectType.Circle) + { + addition = spacingWeight(DistanceTo(previousHitObject), type) * SPACING_WEIGHT_SCALING[(int)type]; + } + + // Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero. + // You will never find maps that require this amongst ranked maps. + addition /= Math.Max(timeElapsed, 50); + + Strains[(int)type] = previousHitObject.Strains[(int)type] * decay + addition; + } + + internal double DistanceTo(OsuHitObjectDifficulty other) + { + // Scale the distance by circle size. + return (normalizedStartPosition - other.normalizedEndPosition).Length; + } + } +} diff --git a/osu.Game.Modes.Osu/Objects/Slider.cs b/osu.Game.Modes.Osu/Objects/Slider.cs index 819e227606..5f6c50fd72 100644 --- a/osu.Game.Modes.Osu/Objects/Slider.cs +++ b/osu.Game.Modes.Osu/Objects/Slider.cs @@ -110,6 +110,8 @@ namespace osu.Game.Modes.Osu.Objects } } } + + public override HitObjectType Type => HitObjectType.Slider; } public enum CurveTypes diff --git a/osu.Game.Modes.Osu/Objects/SliderTick.cs b/osu.Game.Modes.Osu/Objects/SliderTick.cs index de8f3f4b6f..392b1f99ca 100644 --- a/osu.Game.Modes.Osu/Objects/SliderTick.cs +++ b/osu.Game.Modes.Osu/Objects/SliderTick.cs @@ -5,5 +5,7 @@ namespace osu.Game.Modes.Osu.Objects public class SliderTick : OsuHitObject { public int RepeatIndex { get; set; } + + public override HitObjectType Type => HitObjectType.SliderTick; } } diff --git a/osu.Game.Modes.Osu/Objects/Spinner.cs b/osu.Game.Modes.Osu/Objects/Spinner.cs index fa1bddf760..095705eb1f 100644 --- a/osu.Game.Modes.Osu/Objects/Spinner.cs +++ b/osu.Game.Modes.Osu/Objects/Spinner.cs @@ -10,5 +10,7 @@ namespace osu.Game.Modes.Osu.Objects public double Length; public override double EndTime => StartTime + Length; + + public override HitObjectType Type => HitObjectType.Spinner; } } diff --git a/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs b/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs new file mode 100644 index 0000000000..07449f8b6d --- /dev/null +++ b/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs @@ -0,0 +1,193 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Modes.Osu.Objects; +using System; +using System.Collections.Generic; +using osu.Game.Modes.Objects; + +namespace osu.Game.Modes.Osu +{ + public class OsuDifficultyCalculator : DifficultyCalculator + { + private const double STAR_SCALING_FACTOR = 0.0675; + private const double EXTREME_SCALING_FACTOR = 0.5; + + protected override PlayMode PlayMode => PlayMode.Osu; + + /// + /// HitObjects are stored as a member variable. + /// + internal List DifficultyHitObjects = new List(); + + public OsuDifficultyCalculator(Beatmap beatmap) : base(beatmap) + { + } + + protected override HitObjectConverter Converter => new OsuHitObjectConverter(); + + protected override void PreprocessHitObjects() + { + foreach (var h in Objects) + if (h.Type == HitObjectType.Slider) + ((Slider)h).Curve.Calculate(); + } + + protected override double ComputeDifficulty(Dictionary categoryDifficulty) + { + // Fill our custom DifficultyHitObject class, that carries additional information + DifficultyHitObjects.Clear(); + + foreach (var hitObject in Objects) + DifficultyHitObjects.Add(new OsuHitObjectDifficulty(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()) return 0; + + double speedDifficulty = CalculateDifficulty(DifficultyType.Speed); + double aimDifficulty = CalculateDifficulty(DifficultyType.Aim); + + // OverallDifficulty is not considered in this algorithm and neither is HpDrainRate. That means, that in this form the algorithm determines how hard it physically is + // to play the map, assuming, that too much of an error will not lead to a death. + // It might be desirable to include OverallDifficulty into map difficulty, but in my personal opinion it belongs more to the weighting of the actual peformance + // and is superfluous in the beatmap difficulty rating. + // If it were to be considered, then I would look at the hit window of normal HitCircles only, since Sliders and Spinners are (almost) "free" 300s and take map length + // into account as well. + + // The difficulty can be scaled by any desired metric. + // In osu!tp it gets squared to account for the rapid increase in difficulty as the limit of a human is approached. (Of course it also gets scaled afterwards.) + // It would not be suitable for a star rating, therefore: + + // The following is a proposal to forge a star rating from 0 to 5. It consists of taking the square root of the difficulty, since by simply scaling the easier + // 5-star maps would end up with one star. + double speedStars = Math.Sqrt(speedDifficulty) * STAR_SCALING_FACTOR; + double aimStars = Math.Sqrt(aimDifficulty) * STAR_SCALING_FACTOR; + + if (categoryDifficulty != null) + { + categoryDifficulty.Add("Aim", aimStars.ToString("0.00")); + categoryDifficulty.Add("Speed", speedStars.ToString("0.00")); + + double hitWindow300 = 30/*HitObjectManager.HitWindow300*/ / TimeRate; + double preEmpt = 450/*HitObjectManager.PreEmpt*/ / TimeRate; + + categoryDifficulty.Add("OD", (-(hitWindow300 - 80.0) / 6.0).ToString("0.00")); + categoryDifficulty.Add("AR", (preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0).ToString("0.00")); + + int maxCombo = 0; + foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects) + maxCombo += hitObject.MaxCombo; + + categoryDifficulty.Add("Max combo", maxCombo.ToString()); + } + + // Again, from own observations and from the general opinion of the community a map with high speed and low aim (or vice versa) difficulty is harder, + // than a map with mediocre difficulty in both. Therefore we can not just add both difficulties together, but will introduce a scaling that favors extremes. + double starRating = speedStars + aimStars + Math.Abs(speedStars - aimStars) * EXTREME_SCALING_FACTOR; + // Another approach to this would be taking Speed and Aim separately to a chosen power, which again would be equivalent. This would be more convenient if + // the hit window size is to be considered as well. + + // Note: The star rating is tuned extremely tight! Airman (/b/104229) and Freedom Dive (/b/126645), two of the hardest ranked maps, both score ~4.66 stars. + // Expect the easier kind of maps that officially get 5 stars to obtain around 2 by this metric. The tutorial still scores about half a star. + // Tune by yourself as you please. ;) + + return starRating; + } + + protected bool CalculateStrainValues() + { + // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. + List.Enumerator hitObjectsEnumerator = DifficultyHitObjects.GetEnumerator(); + + if (!hitObjectsEnumerator.MoveNext()) return false; + + OsuHitObjectDifficulty currentHitObject = hitObjectsEnumerator.Current; + OsuHitObjectDifficulty nextHitObject; + + // 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()) + { + nextHitObject = hitObjectsEnumerator.Current; + nextHitObject.CalculateStrains(currentHitObject, TimeRate); + currentHitObject = nextHitObject; + } + + return true; + } + + /// + /// 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. + /// + protected const double STRAIN_STEP = 400; + + /// + /// The weighting of each strain value decays to this number * it's previous value + /// + protected const double DECAY_WEIGHT = 0.9; + + protected double CalculateDifficulty(DifficultyType type) + { + 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 + + OsuHitObjectDifficulty previousHitObject = null; + foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects) + { + // 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(OsuHitObjectDifficulty.DECAY_BASE[(int)type], (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + maximumStrain = previousHitObject.Strains[(int)type] * decay; + } + + // Go to the next time interval + intervalEndTime += actualStrainStep; + } + + // Obtain maximum strain + maximumStrain = Math.Max(hitObject.Strains[(int)type], 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; + } + + // Those values are used as array indices. Be careful when changing them! + public enum DifficultyType : int + { + Speed = 0, + Aim, + }; + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Osu/OsuRuleset.cs b/osu.Game.Modes.Osu/OsuRuleset.cs index 398060aa73..d05c8193cc 100644 --- a/osu.Game.Modes.Osu/OsuRuleset.cs +++ b/osu.Game.Modes.Osu/OsuRuleset.cs @@ -40,6 +40,8 @@ namespace osu.Game.Modes.Osu public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => new OsuScoreProcessor(hitObjectCount); + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap); + protected override PlayMode PlayMode => PlayMode.Osu; } } diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj index 1b1ded3d61..ffe408ad59 100644 --- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj @@ -66,9 +66,11 @@ + + diff --git a/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs new file mode 100644 index 0000000000..69b86a86af --- /dev/null +++ b/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Modes.Objects; +using osu.Game.Modes.Taiko.Objects; +using System; +using System.Collections.Generic; + +namespace osu.Game.Modes.Taiko +{ + public class TaikoDifficultyCalculator : DifficultyCalculator + { + protected override PlayMode PlayMode => PlayMode.Taiko; + + public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap) + { + } + + protected override HitObjectConverter Converter => new TaikoConverter(); + + protected override double ComputeDifficulty(Dictionary categoryDifficulty) + { + return 0; + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/TaikoRuleset.cs b/osu.Game.Modes.Taiko/TaikoRuleset.cs index bd20608d57..01e1598e20 100644 --- a/osu.Game.Modes.Taiko/TaikoRuleset.cs +++ b/osu.Game.Modes.Taiko/TaikoRuleset.cs @@ -2,10 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; using osu.Game.Graphics; using osu.Game.Modes.Objects; -using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.UI; using osu.Game.Modes.Taiko.UI; using osu.Game.Modes.UI; @@ -26,5 +24,7 @@ namespace osu.Game.Modes.Taiko public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap); } } diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index 47c71ce09b..6d09463d93 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -47,6 +47,7 @@ + diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs index 6613b5c370..e0d17badcd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs @@ -1,18 +1,15 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.IO; using NUnit.Framework; using OpenTK; using OpenTK.Graphics; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Samples; using osu.Game.Modes; using osu.Game.Modes.Osu; using osu.Game.Modes.Osu.Objects; -using osu.Game.Screens.Play; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Beatmaps.Formats diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs new file mode 100644 index 0000000000..14b6ce3e96 --- /dev/null +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes; +using osu.Game.Modes.Objects; +using System; +using System.Collections.Generic; + +namespace osu.Game.Beatmaps +{ + public abstract class DifficultyCalculator + { + protected abstract PlayMode PlayMode { get; } + + protected double TimeRate = 1; + + protected abstract double ComputeDifficulty(Dictionary categoryDifficulty); + + private void loadTiming() + { + // TODO: Handle mods + int audioRate = 100; + TimeRate = audioRate / 100.0; + } + + public double GetDifficulty(Dictionary categoryDifficulty = null) + { + loadTiming(); + double difficulty = ComputeDifficulty(categoryDifficulty); + return difficulty; + } + } + + public abstract class DifficultyCalculator : DifficultyCalculator where T : HitObject + { + protected List Objects; + + protected abstract HitObjectConverter Converter { get; } + + public DifficultyCalculator(Beatmap beatmap) + { + Objects = Converter.Convert(beatmap); + PreprocessHitObjects(); + } + + protected virtual void PreprocessHitObjects() + { + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 9729ddf257..14aff0e7ba 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables } } - public BeatmapGroup(WorkingBeatmap beatmap, BeatmapSetInfo set = null) + public BeatmapGroup(WorkingBeatmap beatmap) { Header = new BeatmapSetHeader(beatmap) { @@ -68,6 +68,7 @@ namespace osu.Game.Beatmaps.Drawables RelativeSizeAxes = Axes.X, }; + BeatmapSet = beatmap.BeatmapSetInfo; BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b) { Alpha = 0, @@ -76,7 +77,6 @@ namespace osu.Game.Beatmaps.Drawables RelativeSizeAxes = Axes.X, }).ToList(); - BeatmapSet = set; Header.AddDifficultyIcons(BeatmapPanels); } diff --git a/osu.Game/Beatmaps/Timing/TimingChange.cs b/osu.Game/Beatmaps/Timing/TimingChange.cs index e86373f157..10a68bb540 100644 --- a/osu.Game/Beatmaps/Timing/TimingChange.cs +++ b/osu.Game/Beatmaps/Timing/TimingChange.cs @@ -1,12 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace osu.Game.Beatmaps.Timing { class TimingChange : ControlPoint diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 676bf2821b..28c2c5f0e2 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -68,7 +68,6 @@ namespace osu.Game.Beatmaps beatmap = decoder?.Decode(stream); } - if (WithStoryboard && beatmap != null && BeatmapSetInfo.StoryboardFile != null) using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile))) decoder?.Decode(stream, beatmap); diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index b2787dcae3..ec797d518e 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -8,6 +8,7 @@ using osu.Game.Modes; using osu.Game.Screens.Play; using SQLite.Net.Attributes; using SQLiteNetExtensions.Attributes; +using osu.Game.Beatmaps; namespace osu.Game.Database { @@ -73,7 +74,23 @@ namespace osu.Game.Database // Metadata public string Version { get; set; } - public float StarDifficulty => BaseDifficulty?.OverallDifficulty ?? 5; //todo: implement properly + //todo: background threaded computation of this + private float starDifficulty = -1; + public float StarDifficulty + { + get + { + return (starDifficulty < 0) ? (BaseDifficulty?.OverallDifficulty ?? 5) : starDifficulty; + } + + set { starDifficulty = value; } + } + + internal void ComputeDifficulty(BeatmapDatabase database) + { + WorkingBeatmap wb = new WorkingBeatmap(this, BeatmapSet, database); + StarDifficulty = (float)Ruleset.GetRuleset(Mode).CreateDifficultyCalculator(wb.Beatmap).GetDifficulty(); + } public bool Equals(BeatmapInfo other) { diff --git a/osu.Game/Modes/Ruleset.cs b/osu.Game/Modes/Ruleset.cs index 5b9373c10b..851e144408 100644 --- a/osu.Game/Modes/Ruleset.cs +++ b/osu.Game/Modes/Ruleset.cs @@ -32,6 +32,8 @@ namespace osu.Game.Modes public abstract HitObjectParser CreateHitObjectParser(); + public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap); + public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType()); protected abstract PlayMode PlayMode { get; } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 706a71b2b6..2dcd957950 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -337,11 +337,13 @@ namespace osu.Game.Screens.Select if (b.Metadata == null) b.Metadata = beatmapSet.Metadata; }); + foreach (var b in beatmapSet.Beatmaps) + b.ComputeDifficulty(database); beatmapSet.Beatmaps = beatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList(); var beatmap = new WorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault(), beatmapSet, database); - var group = new BeatmapGroup(beatmap, beatmapSet) + var group = new BeatmapGroup(beatmap) { SelectionChanged = selectionChanged, StartRequested = b => start() diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 632e35037b..39e2bfcd03 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -66,6 +66,7 @@ + From 502ad4aa538efdc7873236c491e406bc87a9a4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 19 Feb 2017 18:11:14 +0100 Subject: [PATCH 12/15] Fix incorrect naming scheme --- .../Objects/OsuHitObjectDifficulty.cs | 28 +++++++++---------- osu.Game.Modes.Osu/OsuDifficultyCalculator.cs | 10 +++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs b/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs index 637c754ac4..003011c1ec 100644 --- a/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs +++ b/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs @@ -26,8 +26,8 @@ namespace osu.Game.Modes.Osu.Objects /// Of course the border can not be defined clearly, therefore the algorithm has a smooth transition between those values. /// They also are based on tweaking and general feedback. /// - private const double STREAM_SPACING_TRESHOLD = 110, - SINGLE_SPACING_TRESHOLD = 125; + private const double stream_spacing_threshold = 110, + single_spacing_threshold = 125; /// /// Scaling values for weightings to keep aim and speed difficulty in balance. @@ -35,12 +35,12 @@ namespace osu.Game.Modes.Osu.Objects /// /// Found from testing a very large map pool (containing all ranked maps) and keeping the average values the same. /// - private static readonly double[] SPACING_WEIGHT_SCALING = { 1400, 26.25 }; + private static readonly double[] spacing_weight_scaling = { 1400, 26.25 }; /// /// Almost the normed diameter of a circle (104 osu pixel). That is -after- position transforming. /// - private const double ALMOST_DIAMETER = 90; + private const double almost_diameter = 90; internal OsuHitObject BaseHitObject; internal double[] Strains = { 1, 1 }; @@ -122,14 +122,14 @@ namespace osu.Game.Modes.Osu.Objects switch (type) { case OsuDifficultyCalculator.DifficultyType.Speed: - if (distance > SINGLE_SPACING_TRESHOLD) + if (distance > single_spacing_threshold) return 2.5; - else if (distance > STREAM_SPACING_TRESHOLD) - return 1.6 + 0.9 * (distance - STREAM_SPACING_TRESHOLD) / (SINGLE_SPACING_TRESHOLD - STREAM_SPACING_TRESHOLD); - else if (distance > ALMOST_DIAMETER) - return 1.2 + 0.4 * (distance - ALMOST_DIAMETER) / (STREAM_SPACING_TRESHOLD - ALMOST_DIAMETER); - else if (distance > ALMOST_DIAMETER / 2) - return 0.95 + 0.25 * (distance - (ALMOST_DIAMETER / 2)) / (ALMOST_DIAMETER / 2); + else if (distance > stream_spacing_threshold) + return 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold); + else if (distance > almost_diameter) + return 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter); + else if (distance > almost_diameter / 2) + return 0.95 + 0.25 * (distance - (almost_diameter / 2)) / (almost_diameter / 2); else return 0.95; @@ -162,7 +162,7 @@ namespace osu.Game.Modes.Osu.Objects addition = spacingWeight(previousHitObject.lazySliderLength + DistanceTo(previousHitObject), type) * - SPACING_WEIGHT_SCALING[(int)type]; + spacing_weight_scaling[(int)type]; break; case OsuDifficultyCalculator.DifficultyType.Aim: @@ -174,14 +174,14 @@ namespace osu.Game.Modes.Osu.Objects spacingWeight(previousHitObject.lazySliderLength, type) + spacingWeight(DistanceTo(previousHitObject), type) ) * - SPACING_WEIGHT_SCALING[(int)type]; + spacing_weight_scaling[(int)type]; break; } } else if (BaseHitObject.Type == HitObjectType.Circle) { - addition = spacingWeight(DistanceTo(previousHitObject), type) * SPACING_WEIGHT_SCALING[(int)type]; + addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type]; } // Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero. diff --git a/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs b/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs index 07449f8b6d..24648b1726 100644 --- a/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs +++ b/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs @@ -11,8 +11,8 @@ namespace osu.Game.Modes.Osu { public class OsuDifficultyCalculator : DifficultyCalculator { - private const double STAR_SCALING_FACTOR = 0.0675; - private const double EXTREME_SCALING_FACTOR = 0.5; + private const double star_scaling_factor = 0.0675; + private const double extreme_scaling_factor = 0.5; protected override PlayMode PlayMode => PlayMode.Osu; @@ -63,8 +63,8 @@ namespace osu.Game.Modes.Osu // The following is a proposal to forge a star rating from 0 to 5. It consists of taking the square root of the difficulty, since by simply scaling the easier // 5-star maps would end up with one star. - double speedStars = Math.Sqrt(speedDifficulty) * STAR_SCALING_FACTOR; - double aimStars = Math.Sqrt(aimDifficulty) * STAR_SCALING_FACTOR; + double speedStars = Math.Sqrt(speedDifficulty) * star_scaling_factor; + double aimStars = Math.Sqrt(aimDifficulty) * star_scaling_factor; if (categoryDifficulty != null) { @@ -86,7 +86,7 @@ namespace osu.Game.Modes.Osu // Again, from own observations and from the general opinion of the community a map with high speed and low aim (or vice versa) difficulty is harder, // than a map with mediocre difficulty in both. Therefore we can not just add both difficulties together, but will introduce a scaling that favors extremes. - double starRating = speedStars + aimStars + Math.Abs(speedStars - aimStars) * EXTREME_SCALING_FACTOR; + double starRating = speedStars + aimStars + Math.Abs(speedStars - aimStars) * extreme_scaling_factor; // Another approach to this would be taking Speed and Aim separately to a chosen power, which again would be equivalent. This would be more convenient if // the hit window size is to be considered as well. From 7a0cea332fc3b12b441bf64ffbbcbfb5353050b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 19 Feb 2017 18:21:45 +0100 Subject: [PATCH 13/15] Add support for varying circle size --- .../Objects/OsuHitObjectDifficulty.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs b/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs index 003011c1ec..4f0dac7407 100644 --- a/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs +++ b/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs @@ -47,10 +47,12 @@ namespace osu.Game.Modes.Osu.Objects internal int MaxCombo = 1; - private Vector2 normalizedStartPosition; - private Vector2 normalizedEndPosition; + private float scalingFactor; private float lazySliderLength; + private Vector2 startPosition; + private Vector2 endPosition; + internal OsuHitObjectDifficulty(OsuHitObject baseHitObject) { BaseHitObject = baseHitObject; @@ -61,16 +63,15 @@ namespace osu.Game.Modes.Osu.Objects MaxCombo += slider.Ticks.Count(); // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. - float scalingFactor = (52.0f / circleRadius); + scalingFactor = (52.0f / circleRadius); if (circleRadius < 30) { float smallCircleBonus = Math.Min(30.0f - circleRadius, 5.0f) / 50.0f; scalingFactor *= 1.0f + smallCircleBonus; } - normalizedStartPosition = BaseHitObject.StackedPosition * scalingFactor; - lazySliderLength = 0; + startPosition = baseHitObject.StackedPosition; // Calculate approximation of lazy movement on the slider if (slider != null) @@ -78,7 +79,7 @@ namespace osu.Game.Modes.Osu.Objects float sliderFollowCircleRadius = circleRadius * 3; // Not sure if this is correct, but here we do not need 100% exact values. This comes pretty darn close in my tests. // For simplifying this step we use actual osu! coordinates and simply scale the length, that we obtain by the ScalingFactor later - Vector2 cursorPos = baseHitObject.StackedPosition; + Vector2 cursorPos = startPosition; Action addSliderVertex = delegate (Vector2 pos) { @@ -103,11 +104,11 @@ namespace osu.Game.Modes.Osu.Objects addSliderVertex(baseHitObject.StackedEndPosition); lazySliderLength *= scalingFactor; - normalizedEndPosition = cursorPos * scalingFactor; + endPosition = cursorPos; } // We have a normal HitCircle or a spinner else - normalizedEndPosition = normalizedStartPosition; + endPosition = startPosition; } internal void CalculateStrains(OsuHitObjectDifficulty previousHitObject, double timeRate) @@ -194,7 +195,7 @@ namespace osu.Game.Modes.Osu.Objects internal double DistanceTo(OsuHitObjectDifficulty other) { // Scale the distance by circle size. - return (normalizedStartPosition - other.normalizedEndPosition).Length; + return (startPosition - other.endPosition).Length * scalingFactor; } } } From 7d08ccaae483e8685aafc2c40d7881d9a174eb4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Feb 2017 15:30:58 +0900 Subject: [PATCH 14/15] Update framework. --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 697d8b7e95..b3f409ffb0 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 697d8b7e9530ba7914d9c78012329ce1559e3681 +Subproject commit b3f409ffb027f1b16c639c7ce3bb9dc4f215f79b From a621a65eb56449a031247ef8a6bf0e0448e5a2a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Feb 2017 15:27:48 +0900 Subject: [PATCH 15/15] Fix GlobalHotkeys not working (regression due to masking rules). --- osu.Game/Input/GlobalHotkeys.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Input/GlobalHotkeys.cs b/osu.Game/Input/GlobalHotkeys.cs index 96f8036aed..b1b22f7069 100644 --- a/osu.Game/Input/GlobalHotkeys.cs +++ b/osu.Game/Input/GlobalHotkeys.cs @@ -13,6 +13,11 @@ namespace osu.Game.Input public override bool HandleInput => true; + public GlobalHotkeys() + { + RelativeSizeAxes = Axes.Both; + } + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { return Handler(state, args);