diff --git a/.editorconfig b/.editorconfig
index b5333ad8e7..8cdb92d11c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -176,8 +176,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = false:none
-csharp_style_prefer_range_operator = false:none
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
diff --git a/build/InspectCode.cake b/build/InspectCode.cake
index bd3fdf5f93..06c56dce87 100644
--- a/build/InspectCode.cake
+++ b/build/InspectCode.cake
@@ -1,5 +1,5 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
-#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
+#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.0"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
diff --git a/global.json b/global.json
index 43bb34912a..6858d4044d 100644
--- a/global.json
+++ b/global.json
@@ -1,4 +1,9 @@
{
+ "sdk": {
+ "allowPrerelease": false,
+ "rollForward": "minor",
+ "version": "3.1.100"
+ },
"msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.24"
}
diff --git a/osu.Android.props b/osu.Android.props
index 252570a150..abb3cc8244 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -53,7 +53,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index ab3c040b4e..74a9c05bf9 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return base.CreatePlayer(ruleset);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 02a017ce45..1eb913e900 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void addToPlayfield(DrawableCatchHitObject drawable)
{
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawableRuleset.Playfield.Add(drawable);
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
index f6d26addaa..8c3dfef39c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Game.Rulesets.Catch.Mods;
namespace osu.Game.Rulesets.Catch.Tests
@@ -12,9 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
- public TestSceneDrawableHitObjectsHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new CatchModHidden() };
- }
+ SelectedMods.Value = new[] { new CatchModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index d5d99640af..a4ed966abb 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Catch.Objects
if (value != null)
{
- path.ControlPoints.AddRange(value.ControlPoints);
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 9069c09ae4..6e4491de94 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
prevNoteTimes.RemoveAt(0);
prevNoteTimes.Add(newNoteTime);
- density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
+ density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
}
private double lastTime;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
index 059cd39641..4f7ab87fad 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
@@ -5,7 +5,7 @@ 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;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
- var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+ var endTime = maniaCurrent.BaseObject.GetEndTime();
try
{
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
index ed25173d38..bbbb93fd8b 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
@@ -4,7 +4,7 @@
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;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
- var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+ var endTime = maniaCurrent.BaseObject.GetEndTime();
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
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index 64f353c4d9..098e277fff 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableHitCircle(circle, auto);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
return drawable;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
index 55c6b22146..21ebce8c23 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneHitCircleHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
index 64e7632b1b..412effe176 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
+ SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
return base.CreatePlayer(ruleset);
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index a9d5c03517..e8386363be 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableSlider(slider);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawable.OnNewResult += onNewResult;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
index 2a9c1d167b..d0ee1bddb5 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneSliderHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 5f75cbabec..b6fc9821a4 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -286,11 +286,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
- private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
+ private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
- private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great;
+ private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great;
- private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss;
+ private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss;
private ScoreAccessibleReplayPlayer currentPlayer;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index 3ed3f3e981..f53b64c729 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Depth = depthIndex++
};
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
index a0ab1908d6..dd863deed2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneSpinnerHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
new file mode 100644
index 0000000000..0fc441fec6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
@@ -0,0 +1,75 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Lines;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
+{
+ ///
+ /// A visualisation of the line between two s.
+ ///
+ public class PathControlPointConnectionPiece : CompositeDrawable
+ {
+ public PathControlPoint ControlPoint;
+
+ private readonly Path path;
+ private readonly Slider slider;
+
+ private IBindable sliderPosition;
+ private IBindable pathVersion;
+
+ public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint)
+ {
+ this.slider = slider;
+ ControlPoint = controlPoint;
+
+ Origin = Anchor.Centre;
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = path = new SmoothPath
+ {
+ Anchor = Anchor.Centre,
+ PathRadius = 1
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ sliderPosition = slider.PositionBindable.GetBoundCopy();
+ sliderPosition.BindValueChanged(_ => updateConnectingPath());
+
+ pathVersion = slider.Path.Version.GetBoundCopy();
+ pathVersion.BindValueChanged(_ => updateConnectingPath());
+
+ updateConnectingPath();
+ }
+
+ ///
+ /// Updates the path connecting this control point to the next one.
+ ///
+ private void updateConnectingPath()
+ {
+ Position = slider.StackedPosition + ControlPoint.Position.Value;
+
+ path.ClearVertices();
+
+ int index = slider.Path.ControlPoints.IndexOf(ControlPoint) + 1;
+
+ if (index == 0 || index == slider.Path.ControlPoints.Count)
+ return;
+
+ path.AddVertex(Vector2.Zero);
+ path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value);
+
+ path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index c2aefac587..6a0730db91 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
@@ -19,6 +18,9 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
+ ///
+ /// A visualisation of a single in a .
+ ///
public class PathControlPointPiece : BlueprintPiece
{
public Action RequestSelection;
@@ -28,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public readonly PathControlPoint ControlPoint;
private readonly Slider slider;
- private readonly Path path;
private readonly Container marker;
private readonly Drawable markerRing;
@@ -39,12 +40,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private OsuColour colours { get; set; }
private IBindable sliderPosition;
- private IBindable pathVersion;
+ private IBindable controlPointPosition;
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
{
this.slider = slider;
-
ControlPoint = controlPoint;
Origin = Anchor.Centre;
@@ -52,11 +52,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
InternalChildren = new Drawable[]
{
- path = new SmoothPath
- {
- Anchor = Anchor.Centre,
- PathRadius = 1
- },
marker = new Container
{
Anchor = Anchor.Centre,
@@ -96,20 +91,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
base.LoadComplete();
sliderPosition = slider.PositionBindable.GetBoundCopy();
- sliderPosition.BindValueChanged(_ => updateDisplay());
+ sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
- pathVersion = slider.Path.Version.GetBoundCopy();
- pathVersion.BindValueChanged(_ => updateDisplay());
+ controlPointPosition = ControlPoint.Position.GetBoundCopy();
+ controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
- updateDisplay();
- }
-
- private void updateDisplay()
- {
updateMarkerDisplay();
- updateConnectingPath();
}
// The connecting path is excluded from positional input
@@ -189,26 +178,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
colour = Color4.White;
marker.Colour = colour;
}
-
- ///
- /// Updates the path connecting this control point to the previous one.
- ///
- private void updateConnectingPath()
- {
- path.ClearVertices();
-
- int index = slider.Path.ControlPoints.IndexOf(ControlPoint);
-
- if (index == -1)
- return;
-
- if (++index != slider.Path.ControlPoints.Count)
- {
- path.AddVertex(Vector2.Zero);
- path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value);
- }
-
- path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index cd19653a2e..6f583d7983 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
internal readonly Container Pieces;
+ private readonly Container connections;
+
private readonly Slider slider;
private readonly bool allowSelection;
@@ -42,7 +44,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
RelativeSizeAxes = Axes.Both;
- InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both };
+ InternalChildren = new Drawable[]
+ {
+ connections = new Container { RelativeSizeAxes = Axes.Both },
+ Pieces = new Container { RelativeSizeAxes = Axes.Both }
+ };
}
protected override void LoadComplete()
@@ -62,19 +68,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
foreach (var point in controlPoints)
{
- var piece = new PathControlPointPiece(slider, point);
+ Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
+ {
+ if (allowSelection)
+ d.RequestSelection = selectPiece;
+ }));
- if (allowSelection)
- piece.RequestSelection = selectPiece;
-
- Pieces.Add(piece);
+ connections.Add(new PathControlPointConnectionPiece(slider, point));
}
}
private void removeControlPoints(IEnumerable controlPoints)
{
foreach (var point in controlPoints)
+ {
Pieces.RemoveAll(p => p.ControlPoint == point);
+ connections.RemoveAll(c => c.ControlPoint == point);
+ }
}
protected override bool OnClick(ClickEvent e)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 9b820261ab..2497e428fc 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override bool OnDoubleClick(DoubleClickEvent e)
{
// Todo: This should all not occur on double click, but rather if the previous control point is hovered.
- segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1];
+ segmentStart = HitObject.Path.ControlPoints[^1];
segmentStart.Type.Value = PathType.Linear;
currentSegmentLength = 1;
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 34e5a7f3cd..fe65ab78d1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects
if (value != null)
{
- path.ControlPoints.AddRange(value.ControlPoints);
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
}
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index bd59e8a03f..2686ba4fd2 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays
// TODO: Shouldn't the spinner always spin in the same direction?
if (h is Spinner)
{
- calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection);
+ calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection);
- Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position;
+ Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position;
if (spinCentreOffset.Length > SPIN_RADIUS)
{
@@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Replays
private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing)
{
- OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1];
+ OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1];
// Wait until Auto could "see and react" to the next note.
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
@@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Replays
}
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
- if (Frames[Frames.Count - 1].Time <= endFrame.Time)
+ if (Frames[^1].Time <= endFrame.Time)
AddFrameToReplay(endFrame);
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
index 02152fa51e..e96bd29ad5 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
@@ -28,18 +28,18 @@ namespace osu.Game.Rulesets.Osu.Skinning
InternalChildren = new[]
{
+ ExpandTarget = new NonPlayfieldSprite
+ {
+ Texture = skin.GetTexture("cursor"),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
new NonPlayfieldSprite
{
Texture = skin.GetTexture("cursormiddle"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- ExpandTarget = new NonPlayfieldSprite
- {
- Texture = skin.GetTexture("cursor"),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }
};
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 80291c002e..4e86662ec6 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void addPart(Vector2 screenSpacePosition)
{
parts[currentIndex].Position = screenSpacePosition;
- parts[currentIndex].Time = time;
+ parts[currentIndex].Time = time + 1;
++parts[currentIndex].InvalidationID;
currentIndex = (currentIndex + 1) % max_sprites;
@@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 size;
- private readonly TrailBatch vertexBatch = new TrailBatch(max_sprites, 1);
+ private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1);
public TrailDrawNode(CursorTrail source)
: base(source)
@@ -227,23 +227,52 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Bind();
shader.GetUniform("g_FadeClock").UpdateValue(ref time);
- for (int i = 0; i < parts.Length; ++i)
+ texture.TextureGL.Bind();
+
+ RectangleF textureRect = texture.GetTextureRect();
+
+ foreach (var part in parts)
{
- if (parts[i].InvalidationID == -1)
+ if (part.InvalidationID == -1)
continue;
- vertexBatch.DrawTime = parts[i].Time;
+ if (time - part.Time >= 1)
+ continue;
- Vector2 pos = parts[i].Position;
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
+ TexturePosition = textureRect.BottomLeft,
+ Colour = DrawColourInfo.Colour.BottomLeft.Linear,
+ Time = part.Time
+ });
- DrawQuad(
- texture,
- new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
- DrawColourInfo.Colour,
- null,
- vertexBatch.AddAction);
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
+ TexturePosition = textureRect.BottomRight,
+ Colour = DrawColourInfo.Colour.BottomRight.Linear,
+ Time = part.Time
+ });
+
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
+ TexturePosition = textureRect.TopRight,
+ Colour = DrawColourInfo.Colour.TopRight.Linear,
+ Time = part.Time
+ });
+
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
+ TexturePosition = textureRect.TopLeft,
+ Colour = DrawColourInfo.Colour.TopLeft.Linear,
+ Time = part.Time
+ });
}
+ vertexBatch.Draw();
shader.Unbind();
}
@@ -253,25 +282,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Dispose();
}
-
- // Todo: This shouldn't exist, but is currently used to reduce allocations by caching variable-capturing closures.
- private class TrailBatch : QuadBatch
- {
- public new readonly Action AddAction;
- public float DrawTime;
-
- public TrailBatch(int size, int maxBuffers)
- : base(size, maxBuffers)
- {
- AddAction = v => Add(new TexturedTrailVertex
- {
- Position = v.Position,
- TexturePosition = v.TexturePosition,
- Time = DrawTime + 1,
- Colour = v.Colour,
- });
- }
- }
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
index d0db193738..140433a523 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
return new ScoreAccessiblePlayer();
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 6f4fbd0651..c41727557b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -3,7 +3,6 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Scoring;
@@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
base.CreateNestedHitObjects();
if (IsStrong)
- AddNested(new StrongHitObject { StartTime = (this as IHasEndTime)?.EndTime ?? StartTime });
+ AddNested(new StrongHitObject { StartTime = this.GetEndTime() });
}
public override Judgement CreateJudgement() => new TaikoJudgement();
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index e61953aeb8..4b234b56d4 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Replays;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Beatmaps;
@@ -39,9 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
TaikoHitObject h = Beatmap.HitObjects[i];
-
- IHasEndTime endTimeData = h as IHasEndTime;
- double endTime = endTimeData?.EndTime ?? h.StartTime;
+ double endTime = h.GetEndTime();
switch (h)
{
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 2d2726bbd3..589ec7e8aa 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddStep("Set default user settings", () =>
{
- Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
songSelect.DimLevel.Value = 0.7f;
songSelect.BlurLevel.Value = 0.4f;
});
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index 5ee109e3dd..069b965d9b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new ScoreAccessiblePlayer();
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
index f4e8a68819..992c47f856 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Array.Empty();
+ SelectedMods.Value = Array.Empty();
return new FailPlayer();
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index cca6301b02..1580aac8c5 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Array.Empty();
+ SelectedMods.Value = Array.Empty();
return new FailPlayer();
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index b2b58a63fb..5336c720a1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new RulesetExposingPlayer();
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
new file mode 100644
index 0000000000..39c42980ab
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneHUDOverlay : ManualInputManagerTestScene
+ {
+ private HUDOverlay hudOverlay;
+
+ private Drawable hideTarget => hudOverlay.KeyCounter; // best way of checking hideTargets without exposing.
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ [Test]
+ public void TestShownByDefault()
+ {
+ createNew();
+
+ AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
+
+ AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
+ AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
+ }
+
+ [Test]
+ public void TestFadesInOnLoadComplete()
+ {
+ float? initialAlpha = null;
+
+ createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
+ AddUntilStep("wait for load", () => hudOverlay.IsAlive);
+ AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1);
+ }
+
+ [Test]
+ public void TestHideExternally()
+ {
+ createNew();
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+
+ AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
+ AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
+ }
+
+ [Test]
+ public void TestExternalHideDoesntAffectConfig()
+ {
+ bool originalConfigValue = false;
+
+ createNew();
+
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface));
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+
+ AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+ }
+
+ private void createNew(Action action = null)
+ {
+ AddStep("create overlay", () =>
+ {
+ Child = hudOverlay = new HUDOverlay(null, null, Array.Empty());
+
+ action?.Invoke(hudOverlay);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index f02361e685..f68f4b8b83 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToTrack(Beatmap.Value.Track);
InputManager.Child = container = new TestPlayerLoaderContainer(
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestEarlyExit()
{
- AddStep("load dummy beatmap", () => ResetPlayer(false, () => Mods.Value = new[] { new OsuModNightcore() }));
+ AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
AddStep("exit loader", () => loader.Exit());
@@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay
TestMod playerMod1 = null;
TestMod playerMod2 = null;
- AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); });
+ AddStep("load player", () => { ResetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); });
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
index 2a45e68c0a..96c0c59695 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set second set", () => details.BeatmapSet = secondSet);
AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics);
- BeatmapSetInfo createSet() => new BeatmapSetInfo
+ static BeatmapSetInfo createSet() => new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
Beatmaps = new List
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 5dd02c1ddd..00fa95bedc 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -256,17 +256,17 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("change ruleset", () =>
{
- Mods.ValueChanged += onModChange;
+ SelectedMods.ValueChanged += onModChange;
songSelect.Ruleset.ValueChanged += onRulesetChange;
Ruleset.Value = new TaikoRuleset().RulesetInfo;
- Mods.ValueChanged -= onModChange;
+ SelectedMods.ValueChanged -= onModChange;
songSelect.Ruleset.ValueChanged -= onRulesetChange;
});
AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex);
- AddAssert("empty mods", () => !Mods.Value.Any());
+ AddAssert("empty mods", () => !SelectedMods.Value.Any());
void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++;
void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++;
@@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestModsRetainedBetweenSongSelect()
{
- AddAssert("empty mods", () => !Mods.Value.Any());
+ AddAssert("empty mods", () => !SelectedMods.Value.Any());
createSongSelect();
@@ -285,7 +285,7 @@ namespace osu.Game.Tests.Visual.SongSelect
createSongSelect();
- AddAssert("mods retained", () => Mods.Value.Any());
+ AddAssert("mods retained", () => SelectedMods.Value.Any());
}
[Test]
@@ -332,7 +332,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
- private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods);
+ private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods);
private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id));
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index ed44d82bce..b0b673d6a4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{
- if (timingPoints[timingPoints.Count - 1] == current)
+ if (timingPoints[^1] == current)
return current;
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
@@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
if (timingPoints.Count == 0) return 0;
- if (timingPoints[timingPoints.Count - 1] == current)
+ if (timingPoints[^1] == current)
return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index be50200e1c..12ee4ceb2e 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -8,13 +8,16 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Mods.Sections;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.HUD;
@@ -48,42 +51,48 @@ namespace osu.Game.Tests.Visual.UserInterface
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
+ }
- Add(modSelect = new TestModSelectOverlay
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.X,
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.BottomCentre,
- });
+ modSelect = new TestModSelectOverlay
+ {
+ RelativeSizeAxes = Axes.X,
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ },
- Add(modDisplay = new ModDisplay
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- AutoSizeAxes = Axes.Both,
- Position = new Vector2(0, 25),
- });
+ modDisplay = new ModDisplay
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Position = new Vector2(0, 25),
+ Current = { BindTarget = modSelect.SelectedMods }
+ }
+ };
+ });
- modDisplay.Current.UnbindBindings();
- modDisplay.Current.BindTo(modSelect.SelectedMods);
-
- AddStep("Show", modSelect.Show);
- AddStep("Toggle", modSelect.ToggleVisibility);
- AddStep("Toggle", modSelect.ToggleVisibility);
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("show", () => modSelect.Show());
}
[Test]
public void TestOsuMods()
{
- var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 0);
- changeRuleset(ruleset);
+ changeRuleset(0);
- var instance = ruleset.CreateInstance();
+ var osu = new OsuRuleset();
- var easierMods = instance.GetModsFor(ModType.DifficultyReduction);
- var harderMods = instance.GetModsFor(ModType.DifficultyIncrease);
+ var easierMods = osu.GetModsFor(ModType.DifficultyReduction);
+ var harderMods = osu.GetModsFor(ModType.DifficultyIncrease);
- var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
+ var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
@@ -97,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiMod(doubleTimeMod);
testIncompatibleMods(easy, hardRock);
testDeselectAll(easierMods.Where(m => !(m is MultiMod)));
- testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
- testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
+ testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour);
+ testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour);
testUnimplementedMod(spunOutMod);
}
@@ -106,37 +115,31 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestManiaMods()
{
- var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 3);
- changeRuleset(ruleset);
+ changeRuleset(3);
- testRankedText(ruleset.CreateInstance().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom));
+ testRankedText(new ManiaRuleset().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom));
}
[Test]
public void TestRulesetChanges()
{
- var rulesetOsu = rulesets.AvailableRulesets.First(r => r.ID == 0);
- var rulesetMania = rulesets.AvailableRulesets.First(r => r.ID == 3);
+ changeRuleset(0);
- changeRuleset(null);
+ var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
- var instance = rulesetOsu.CreateInstance();
- var easierMods = instance.GetModsFor(ModType.DifficultyReduction);
- var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
+ AddStep("set mods externally", () => { SelectedMods.Value = new[] { noFailMod }; });
- AddStep("set mods externally", () => { modDisplay.Current.Value = new[] { noFailMod }; });
-
- changeRuleset(rulesetOsu);
+ changeRuleset(0);
AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null);
- changeRuleset(rulesetMania);
+ changeRuleset(3);
- AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any(m => m is OsuModNoFail));
+ AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0);
- changeRuleset(rulesetOsu);
+ changeRuleset(0);
- AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any());
+ AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0);
}
private void testSingleMod(Mod mod)
@@ -198,19 +201,19 @@ namespace osu.Game.Tests.Visual.UserInterface
selectNext(mod);
AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any());
- AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke);
+ AddStep("deselect all", () => modSelect.DeselectAllButton.Action.Invoke());
AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any());
}
- private void testMultiplierTextColour(Mod mod, Color4 colour)
+ private void testMultiplierTextColour(Mod mod, Func getCorrectColour)
{
- checkLabelColor(Color4.White);
+ checkLabelColor(() => Color4.White);
selectNext(mod);
AddWaitStep("wait for changing colour", 1);
- checkLabelColor(colour);
+ checkLabelColor(getCorrectColour);
selectPrevious(mod);
AddWaitStep("wait for changing colour", 1);
- checkLabelColor(Color4.White);
+ checkLabelColor(() => Color4.White);
}
private void testRankedText(Mod mod)
@@ -235,9 +238,9 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
- private void changeRuleset(RulesetInfo ruleset)
+ private void changeRuleset(int? id)
{
- AddStep($"change ruleset to {ruleset}", () => { Ruleset.Value = ruleset; });
+ AddStep($"change ruleset to {(id?.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); });
waitForLoad();
}
@@ -253,7 +256,7 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
- private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color);
+ private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour());
private class TestModSelectOverlay : ModSelectOverlay
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs
index fc44c5f595..8dcb7dcbf8 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs
@@ -2,15 +2,20 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Allocation;
+using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Tests.Visual.UserInterface
{
@@ -18,28 +23,51 @@ namespace osu.Game.Tests.Visual.UserInterface
{
private TestModSelectOverlay modSelect;
- [BackgroundDependencyLoader]
- private void load()
+ private readonly Mod testCustomisableMod = new TestModCustomisable1();
+
+ [Test]
+ public void TestButtonShowsOnCustomisableMod()
{
- Add(modSelect = new TestModSelectOverlay
- {
- RelativeSizeAxes = Axes.X,
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.BottomCentre,
- });
+ createModSelect();
- var testMod = new TestModCustomisable1();
-
- AddStep("open", modSelect.Show);
+ AddStep("open", () => modSelect.Show());
AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value);
AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded);
- AddStep("select mod", () => modSelect.SelectMod(testMod));
+ AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod));
AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
AddStep("open Customisation", () => modSelect.CustomiseButton.Click());
- AddStep("deselect mod", () => modSelect.SelectMod(testMod));
+ AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod));
AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0);
}
+ [Test]
+ public void TestButtonShowsOnModAlreadyAdded()
+ {
+ AddStep("set active mods", () => SelectedMods.Value = new List { testCustomisableMod });
+
+ createModSelect();
+
+ AddAssert("mods still active", () => SelectedMods.Value.Count == 1);
+
+ AddStep("open", () => modSelect.Show());
+ AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
+ }
+
+ private void createModSelect()
+ {
+ AddStep("create mod select", () =>
+ {
+ Ruleset.Value = new TestRulesetInfo();
+
+ Child = modSelect = new TestModSelectOverlay
+ {
+ RelativeSizeAxes = Axes.X,
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ };
+ });
+ }
+
private class TestModSelectOverlay : ModSelectOverlay
{
public new Container ModSettingsContainer => base.ModSettingsContainer;
@@ -50,24 +78,41 @@ namespace osu.Game.Tests.Visual.UserInterface
public void SelectMod(Mod mod) =>
ModSectionsContainer.Children.Single(s => s.ModType == mod.Type)
.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1);
+ }
- protected override void LoadComplete()
+ public class TestRulesetInfo : RulesetInfo
+ {
+ public override Ruleset CreateInstance() => new TestCustomisableModRuleset();
+
+ public TestRulesetInfo()
{
- base.LoadComplete();
+ Available = true;
+ }
- foreach (var section in ModSectionsContainer)
+ public class TestCustomisableModRuleset : Ruleset
+ {
+ public override IEnumerable GetModsFor(ModType type)
{
- if (section.ModType == ModType.Conversion)
+ if (type == ModType.Conversion)
{
- section.Mods = new Mod[]
+ return new Mod[]
{
new TestModCustomisable1(),
new TestModCustomisable2()
};
}
- else
- section.Mods = Array.Empty();
+
+ return Array.Empty();
}
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
+
+ public override string Description { get; } = "test";
+ public override string ShortName { get; } = "tst";
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index ce2783004c..03496952e7 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -195,8 +195,8 @@ namespace osu.Game.Beatmaps.ControlPoints
if (time < list[0].Time)
return null;
- if (time >= list[list.Count - 1].Time)
- return list[list.Count - 1];
+ if (time >= list[^1].Time)
+ return list[^1];
int l = 0;
int r = list.Count - 2;
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index 2b914669cb..e401e3fb97 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.Formats
if (line.StartsWith(@"[", StringComparison.Ordinal) && line.EndsWith(@"]", StringComparison.Ordinal))
{
- if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
+ if (!Enum.TryParse(line[1..^1], out section))
{
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
section = Section.None;
diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
index 2bbac92f7f..9735f6373d 100644
--- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Containers
foreach (var link in links)
{
- AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd));
+ AddText(text[previousLinkEnd..link.Index]);
AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url);
previousLinkEnd = link.Index + link.Length;
}
diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
index 2721ce55dc..ab72276ad0 100644
--- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
@@ -83,6 +83,15 @@ namespace osu.Game.Graphics.Containers
return base.OnDragEnd(e);
}
+ protected override bool OnScroll(ScrollEvent e)
+ {
+ // allow for controlling volume when alt is held.
+ // mostly for compatibility with osu-stable.
+ if (e.AltPressed) return false;
+
+ return base.OnScroll(e);
+ }
+
protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction);
protected class OsuScrollbar : ScrollbarContainer
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index f310da3883..22b8d9d012 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -80,8 +80,13 @@ namespace osu.Game
// todo: move this to SongSelect once Screen has the ability to unsuspend.
[Cached]
- [Cached(Type = typeof(IBindable>))]
- protected readonly Bindable> Mods = new Bindable>(Array.Empty());
+ [Cached(typeof(IBindable>))]
+ protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty());
+
+ ///
+ /// Mods available for the current .
+ ///
+ public readonly Bindable>> AvailableMods = new Bindable>>();
protected Bindable Beatmap { get; private set; } // cached via load() method
@@ -233,6 +238,23 @@ namespace osu.Game
PreviewTrackManager previewTrackManager;
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
Add(previewTrackManager);
+
+ Ruleset.BindValueChanged(onRulesetChanged);
+ }
+
+ private void onRulesetChanged(ValueChangedEvent r)
+ {
+ var dict = new Dictionary>();
+
+ if (r.NewValue?.Available == true)
+ {
+ foreach (ModType type in Enum.GetValues(typeof(ModType)))
+ dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList();
+ }
+
+ if (!SelectedMods.Disabled)
+ SelectedMods.Value = Array.Empty();
+ AvailableMods.Value = dict;
}
protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer();
diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs
index f4b588ddb3..10b3bc7c2b 100644
--- a/osu.Game/Overlays/Mods/ModControlSection.cs
+++ b/osu.Game/Overlays/Mods/ModControlSection.cs
@@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
@@ -12,14 +12,13 @@ using osuTK;
namespace osu.Game.Overlays.Mods
{
- public class ModControlSection : Container
+ public class ModControlSection : CompositeDrawable
{
protected FillFlowContainer FlowContent;
- protected override Container Content => FlowContent;
public readonly Mod Mod;
- public ModControlSection(Mod mod)
+ public ModControlSection(Mod mod, IEnumerable modControls)
{
Mod = mod;
@@ -33,9 +32,8 @@ namespace osu.Game.Overlays.Mods
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
+ ChildrenEnumerable = modControls
};
-
- AddRange(Mod.CreateSettingsControls());
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index e8ea43e3f2..7f07ce620c 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -20,7 +20,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods.Sections;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens;
using osuTK;
@@ -50,7 +49,7 @@ namespace osu.Game.Overlays.Mods
protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty());
- protected readonly IBindable Ruleset = new Bindable();
+ private Bindable>> availableMods;
protected Color4 LowMultiplierColour;
protected Color4 HighMultiplierColour;
@@ -322,14 +321,14 @@ namespace osu.Game.Overlays.Mods
}
[BackgroundDependencyLoader(true)]
- private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods)
+ private void load(OsuColour colours, AudioManager audio, Bindable> selectedMods, OsuGameBase osu)
{
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
- Ruleset.BindTo(ruleset);
- if (mods != null) SelectedMods.BindTo(mods);
+ availableMods = osu.AvailableMods.GetBoundCopy();
+ SelectedMods.BindTo(selectedMods);
sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off");
@@ -360,7 +359,7 @@ namespace osu.Game.Overlays.Mods
{
base.LoadComplete();
- Ruleset.BindValueChanged(rulesetChanged, true);
+ availableMods.BindValueChanged(availableModsChanged, true);
SelectedMods.BindValueChanged(selectedModsChanged, true);
}
@@ -410,22 +409,12 @@ namespace osu.Game.Overlays.Mods
return base.OnKeyDown(e);
}
- private void rulesetChanged(ValueChangedEvent e)
+ private void availableModsChanged(ValueChangedEvent>> mods)
{
- if (e.NewValue == null) return;
-
- var instance = e.NewValue.CreateInstance();
+ if (mods.NewValue == null) return;
foreach (var section in ModSectionsContainer.Children)
- section.Mods = instance.GetModsFor(section.ModType);
-
- // attempt to re-select any already selected mods.
- // this may be the first time we are receiving the ruleset, in which case they will still match.
- selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value));
-
- // write the mods back to the SelectedMods bindable in the case a change was not applicable.
- // this generally isn't required as the previous line will perform deselection; just here for safety.
- refreshSelectedMods();
+ section.Mods = mods.NewValue[section.ModType];
}
private void selectedModsChanged(ValueChangedEvent> mods)
@@ -462,17 +451,17 @@ namespace osu.Game.Overlays.Mods
private void updateModSettings(ValueChangedEvent> selectedMods)
{
- foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue))
+ ModSettingsContent.Clear();
+
+ foreach (var mod in selectedMods.NewValue)
{
- var controls = added.CreateSettingsControls().ToList();
- if (controls.Count > 0)
- ModSettingsContent.Add(new ModControlSection(added) { Children = controls });
+ var settings = mod.CreateSettingsControls().ToList();
+ if (settings.Count > 0)
+ ModSettingsContent.Add(new ModControlSection(mod, settings));
}
- foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue))
- ModSettingsContent.RemoveAll(section => section.Mod == removed);
+ bool hasSettings = ModSettingsContent.Count > 0;
- bool hasSettings = ModSettingsContent.Children.Count > 0;
CustomiseButton.Enabled.Value = hasSettings;
if (!hasSettings)
@@ -502,8 +491,8 @@ namespace osu.Game.Overlays.Mods
{
base.Dispose(isDisposing);
- Ruleset.UnbindAll();
- SelectedMods.UnbindAll();
+ availableMods?.UnbindAll();
+ SelectedMods?.UnbindAll();
}
#endregion
diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs
index dcb3cb5597..71a666414f 100644
--- a/osu.Game/Rulesets/Mods/ModDaycore.cs
+++ b/osu.Game/Rulesets/Mods/ModDaycore.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.Audio;
using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Mods
@@ -13,9 +15,23 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => FontAwesome.Solid.Question;
public override string Description => "Whoaaaaa...";
+ private readonly BindableNumber tempoAdjust = new BindableDouble(1);
+ private readonly BindableNumber freqAdjust = new BindableDouble(1);
+
+ protected ModDaycore()
+ {
+ SpeedChange.BindValueChanged(val =>
+ {
+ freqAdjust.Value = SpeedChange.Default;
+ tempoAdjust.Value = val.NewValue / SpeedChange.Default;
+ }, true);
+ }
+
public override void ApplyToTrack(Track track)
{
- track.Frequency.Value *= RateAdjust;
+ // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
+ track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
+ track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
}
}
}
diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs
index 5e685b040e..7015460c51 100644
--- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs
+++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs
@@ -3,12 +3,14 @@
using System;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModDoubleTime : ModTimeAdjust
+ public abstract class ModDoubleTime : ModRateAdjust
{
public override string Name => "Double Time";
public override string Acronym => "DT";
@@ -19,6 +21,14 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray();
- protected override double RateAdjust => 1.5;
+ [SettingSource("Speed increase", "The actual increase to apply")]
+ public override BindableNumber SpeedChange { get; } = new BindableDouble
+ {
+ MinValue = 1.01,
+ MaxValue = 2,
+ Default = 1.5,
+ Value = 1.5,
+ Precision = 0.01,
+ };
}
}
diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs
index d17ddd2253..15f7afa312 100644
--- a/osu.Game/Rulesets/Mods/ModHalfTime.cs
+++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs
@@ -3,12 +3,14 @@
using System;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModHalfTime : ModTimeAdjust
+ public abstract class ModHalfTime : ModRateAdjust
{
public override string Name => "Half Time";
public override string Acronym => "HT";
@@ -19,6 +21,14 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray();
- protected override double RateAdjust => 0.75;
+ [SettingSource("Speed decrease", "The actual decrease to apply")]
+ public override BindableNumber SpeedChange { get; } = new BindableDouble
+ {
+ MinValue = 0.5,
+ MaxValue = 0.99,
+ Default = 0.75,
+ Value = 0.75,
+ Precision = 0.01,
+ };
}
}
diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs
index a4f1ef5a72..c14e02e64d 100644
--- a/osu.Game/Rulesets/Mods/ModNightcore.cs
+++ b/osu.Game/Rulesets/Mods/ModNightcore.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.Audio;
using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
@@ -14,9 +16,23 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => OsuIcon.ModNightcore;
public override string Description => "Uguuuuuuuu...";
+ private readonly BindableNumber tempoAdjust = new BindableDouble(1);
+ private readonly BindableNumber freqAdjust = new BindableDouble(1);
+
+ protected ModNightcore()
+ {
+ SpeedChange.BindValueChanged(val =>
+ {
+ freqAdjust.Value = SpeedChange.Default;
+ tempoAdjust.Value = val.NewValue / SpeedChange.Default;
+ }, true);
+ }
+
public override void ApplyToTrack(Track track)
{
- track.Frequency.Value *= RateAdjust;
+ // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
+ track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
+ track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
}
}
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
similarity index 54%
rename from osu.Game/Rulesets/Mods/ModTimeAdjust.cs
rename to osu.Game/Rulesets/Mods/ModRateAdjust.cs
index 7d0cc2a7c3..1739524bcd 100644
--- a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
@@ -1,20 +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 System;
+using osu.Framework.Audio;
using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModTimeAdjust : Mod, IApplicableToTrack
+ public abstract class ModRateAdjust : Mod, IApplicableToTrack
{
- public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) };
-
- protected abstract double RateAdjust { get; }
+ public abstract BindableNumber SpeedChange { get; }
public virtual void ApplyToTrack(Track track)
{
- track.Tempo.Value *= RateAdjust;
+ track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
}
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index 839b2ae36e..133f9ceb39 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -3,42 +3,58 @@
using System;
using System.Linq;
+using osu.Framework.Audio;
using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToTrack, IApplicableToBeatmap
+ public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack
{
///
/// The point in the beatmap at which the final ramping rate should be reached.
///
private const double final_rate_progress = 0.75f;
- public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) };
-
- protected abstract double FinalRateAdjustment { get; }
+ [SettingSource("Final rate", "The final speed to ramp to")]
+ public abstract BindableNumber FinalRate { get; }
private double finalRateTime;
private double beginRampTime;
+
+ public BindableNumber SpeedChange { get; } = new BindableDouble
+ {
+ Default = 1,
+ Value = 1,
+ Precision = 0.01,
+ };
+
private Track track;
- public virtual void ApplyToTrack(Track track)
+ protected ModTimeRamp()
+ {
+ // for preview purpose at song select. eventually we'll want to be able to update every frame.
+ FinalRate.BindValueChanged(val => applyAdjustment(1), true);
+ }
+
+ public void ApplyToTrack(Track track)
{
this.track = track;
+ track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
- lastAdjust = 1;
-
- // for preview purposes. during gameplay, Update will overwrite this setting.
- applyAdjustment(1);
+ FinalRate.TriggerChange();
}
public virtual void ApplyToBeatmap(IBeatmap beatmap)
{
HitObject lastObject = beatmap.HitObjects.LastOrDefault();
+ SpeedChange.SetDefault();
+
beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0;
finalRateTime = final_rate_progress * (lastObject?.GetEndTime() ?? 0);
}
@@ -48,20 +64,11 @@ namespace osu.Game.Rulesets.Mods
applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
}
- private double lastAdjust = 1;
-
///
/// Adjust the rate along the specified ramp
///
/// The amount of adjustment to apply (from 0..1).
- private void applyAdjustment(double amount)
- {
- double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment));
-
- track.Tempo.Value /= lastAdjust;
- track.Tempo.Value *= adjust;
-
- lastAdjust = adjust;
- }
+ private void applyAdjustment(double amount) =>
+ SpeedChange.Value = 1 + (FinalRate.Value - 1) * Math.Clamp(amount, 0, 1);
}
}
diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs
index b2e3abb59d..5416f1ac22 100644
--- a/osu.Game/Rulesets/Mods/ModWindDown.cs
+++ b/osu.Game/Rulesets/Mods/ModWindDown.cs
@@ -3,7 +3,9 @@
using System;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
namespace osu.Game.Rulesets.Mods
{
@@ -15,7 +17,15 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => FontAwesome.Solid.ChevronCircleDown;
public override double ScoreMultiplier => 1.0;
- protected override double FinalRateAdjustment => -0.25;
+ [SettingSource("Final rate", "The speed increase to ramp towards")]
+ public override BindableNumber FinalRate { get; } = new BindableDouble
+ {
+ MinValue = 0.5,
+ MaxValue = 0.99,
+ Default = 0.75,
+ Value = 0.75,
+ Precision = 0.01,
+ };
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();
}
diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs
index 8df35a1de2..3cf584f3dd 100644
--- a/osu.Game/Rulesets/Mods/ModWindUp.cs
+++ b/osu.Game/Rulesets/Mods/ModWindUp.cs
@@ -3,7 +3,9 @@
using System;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
namespace osu.Game.Rulesets.Mods
{
@@ -15,7 +17,15 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => FontAwesome.Solid.ChevronCircleUp;
public override double ScoreMultiplier => 1.0;
- protected override double FinalRateAdjustment => 0.5;
+ [SettingSource("Final rate", "The speed increase to ramp towards")]
+ public override BindableNumber FinalRate { get; } = new BindableDouble
+ {
+ MinValue = 1.01,
+ MaxValue = 2,
+ Default = 1.5,
+ Value = 1.5,
+ Precision = 0.01,
+ };
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();
}
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 1179efaa6e..bd96441ebb 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects
ApplyDefaultsToSelf(controlPointInfo, difficulty);
// This is done here since ApplyDefaultsToSelf may be used to determine the end time
- SampleControlPoint = controlPointInfo.SamplePointAt(((this as IHasEndTime)?.EndTime ?? StartTime) + control_point_leniency);
+ SampleControlPoint = controlPointInfo.SamplePointAt(this.GetEndTime() + control_point_leniency);
nestedHitObjects.Clear();
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index b5b1e26486..7fddb442d1 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples);
// The samples are played when the slider ends, which is the last node
- result.Samples = nodeSamples[nodeSamples.Count - 1];
+ result.Samples = nodeSamples[^1];
}
else if (type.HasFlag(ConvertHitObjectType.Spinner))
{
@@ -279,7 +279,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
{
if (vertices[i] == vertices[i - 1])
{
- points[points.Count - 1].Type.Value = type;
+ points[^1].Type.Value = type;
continue;
}
diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs
index 86deba3b93..293138097f 100644
--- a/osu.Game/Rulesets/Objects/SliderPath.cs
+++ b/osu.Game/Rulesets/Objects/SliderPath.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Objects
get
{
ensureValid();
- return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1];
+ return cumulativeLength.Count == 0 ? 0 : cumulativeLength[^1];
}
}
@@ -251,7 +251,7 @@ namespace osu.Game.Rulesets.Objects
if (calculatedLength > expectedDistance)
{
// The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments
- while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] >= expectedDistance)
+ while (cumulativeLength.Count > 0 && cumulativeLength[^1] >= expectedDistance)
{
cumulativeLength.RemoveAt(cumulativeLength.Count - 1);
calculatedPath.RemoveAt(pathEndIndex--);
@@ -269,7 +269,7 @@ namespace osu.Game.Rulesets.Objects
// The direction of the segment to shorten or lengthen
Vector2 dir = (calculatedPath[pathEndIndex] - calculatedPath[pathEndIndex - 1]).Normalized();
- calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[cumulativeLength.Count - 1]);
+ calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[^1]);
cumulativeLength.Add(expectedDistance);
}
}
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 94165fe4b7..6394fb8d23 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -87,12 +87,12 @@ namespace osu.Game.Screens
public virtual float BackgroundParallaxAmount => 1;
+ public virtual bool AllowRateAdjustments => true;
+
public Bindable Beatmap { get; private set; }
public Bindable Ruleset { get; private set; }
- public virtual bool AllowRateAdjustments => true;
-
public Bindable> Mods { get; private set; }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs
index 115f4b7e1a..8d54829d49 100644
--- a/osu.Game/Screens/OsuScreenDependencies.cs
+++ b/osu.Game/Screens/OsuScreenDependencies.cs
@@ -26,16 +26,26 @@ namespace osu.Game.Screens
Beatmap = parent.Get>()?.GetBoundCopy();
if (Beatmap == null)
+ {
Cache(Beatmap = parent.Get>().BeginLease(false));
+ CacheAs(Beatmap);
+ }
Ruleset = parent.Get>()?.GetBoundCopy();
if (Ruleset == null)
+ {
Cache(Ruleset = parent.Get>().BeginLease(true));
+ CacheAs(Ruleset);
+ }
Mods = parent.Get>>()?.GetBoundCopy();
+
if (Mods == null)
+ {
Cache(Mods = parent.Get>>().BeginLease(true));
+ CacheAs(Mods);
+ }
}
else
{
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 2cc03ae453..9f46fddc5e 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play
private readonly double firstHitObjectTime;
- public readonly Bindable UserPlaybackRate = new BindableDouble(1)
+ public readonly BindableNumber UserPlaybackRate = new BindableDouble(1)
{
Default = 1,
MinValue = 0.5,
@@ -73,7 +73,6 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both;
track = beatmap.Track;
- track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
@@ -120,7 +119,6 @@ namespace osu.Game.Screens.Play
Seek(startTime);
adjustableClock.ProcessFrame();
- UserPlaybackRate.ValueChanged += _ => updateRate();
}
public void Restart()
@@ -223,7 +221,8 @@ namespace osu.Game.Screens.Play
speedAdjustmentsApplied = true;
track.ResetSpeedAdjustments();
- track.Tempo.Value = UserPlaybackRate.Value;
+ track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
+ track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
foreach (var mod in mods.OfType())
mod.ApplyToTrack(track);
@@ -244,8 +243,6 @@ namespace osu.Game.Screens.Play
track.ResetSpeedAdjustments();
speedAdjustmentsApplied = false;
}
-
- track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
}
}
}
diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
index 54556f8648..6196ce4026 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
@@ -31,7 +31,8 @@ namespace osu.Game.Screens.Play.HUD
RelativeSizeAxes = Axes.Both;
- processor.NewJudgement += onNewJudgement;
+ if (processor != null)
+ processor.NewJudgement += onNewJudgement;
}
[BackgroundDependencyLoader]
@@ -96,7 +97,9 @@ namespace osu.Game.Screens.Play.HUD
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
- processor.NewJudgement -= onNewJudgement;
+
+ if (processor != null)
+ processor.NewJudgement -= onNewJudgement;
}
}
}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 0f9edf5606..e2f362780d 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
@@ -23,8 +24,8 @@ namespace osu.Game.Screens.Play
{
public class HUDOverlay : Container
{
- private const int duration = 250;
- private const Easing easing = Easing.OutQuint;
+ private const float fade_duration = 400;
+ private const Easing fade_easing = Easing.Out;
public readonly KeyCounterDisplay KeyCounter;
public readonly RollingCounter ComboCounter;
@@ -43,8 +44,15 @@ namespace osu.Game.Screens.Play
private readonly DrawableRuleset drawableRuleset;
private readonly IReadOnlyList mods;
- private Bindable showHud;
+ ///
+ /// Whether the elements that can optionally be hidden should be visible.
+ ///
+ public Bindable ShowHud { get; } = new BindableBool();
+
+ private Bindable configShowHud;
+
private readonly Container visibilityContainer;
+
private readonly BindableBool replayLoaded = new BindableBool();
private static bool hasShownNotificationOnce;
@@ -53,6 +61,8 @@ namespace osu.Game.Screens.Play
private readonly Container topScoreContainer;
+ private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
+
public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods)
{
this.scoreProcessor = scoreProcessor;
@@ -68,13 +78,12 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
+ HealthDisplay = CreateHealthDisplay(),
topScoreContainer = new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
- AutoSizeDuration = 200,
- AutoSizeEasing = Easing.Out,
Children = new Drawable[]
{
AccuracyCounter = CreateAccuracyCounter(),
@@ -82,19 +91,20 @@ namespace osu.Game.Screens.Play
ComboCounter = CreateComboCounter(),
},
},
- HealthDisplay = CreateHealthDisplay(),
Progress = CreateProgress(),
ModDisplay = CreateModsContainer(),
HitErrorDisplay = CreateHitErrorDisplayOverlay(),
+ PlayerSettingsOverlay = CreatePlayerSettingsOverlay(),
}
},
- PlayerSettingsOverlay = CreatePlayerSettingsOverlay(),
new FillFlowContainer
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y),
AutoSizeAxes = Axes.Both,
+ LayoutDuration = fade_duration / 2,
+ LayoutEasing = fade_easing,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
@@ -108,34 +118,24 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
{
- BindProcessor(scoreProcessor);
- BindDrawableRuleset(drawableRuleset);
+ if (scoreProcessor != null)
+ BindProcessor(scoreProcessor);
- Progress.Objects = drawableRuleset.Objects;
- Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value;
- Progress.RequestSeek = time => RequestSeek(time);
- Progress.ReferenceClock = drawableRuleset.FrameStableClock;
+ if (drawableRuleset != null)
+ {
+ BindDrawableRuleset(drawableRuleset);
+
+ Progress.Objects = drawableRuleset.Objects;
+ Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value;
+ Progress.RequestSeek = time => RequestSeek(time);
+ Progress.ReferenceClock = drawableRuleset.FrameStableClock;
+ }
ModDisplay.Current.Value = mods;
- showHud = config.GetBindable(OsuSetting.ShowInterface);
- showHud.BindValueChanged(visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration, easing), true);
+ configShowHud = config.GetBindable(OsuSetting.ShowInterface);
- ShowHealthbar.BindValueChanged(healthBar =>
- {
- if (healthBar.NewValue)
- {
- HealthDisplay.FadeIn(duration, easing);
- topScoreContainer.MoveToY(30, duration, easing);
- }
- else
- {
- HealthDisplay.FadeOut(duration, easing);
- topScoreContainer.MoveToY(0, duration, easing);
- }
- }, true);
-
- if (!showHud.Value && !hasShownNotificationOnce)
+ if (!configShowHud.Value && !hasShownNotificationOnce)
{
hasShownNotificationOnce = true;
@@ -144,12 +144,39 @@ namespace osu.Game.Screens.Play
Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab."
});
}
+
+ // start all elements hidden
+ hideTargets.ForEach(d => d.Hide());
}
+ public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead.");
+
protected override void LoadComplete()
{
base.LoadComplete();
+ ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, fade_duration, fade_easing)));
+
+ ShowHealthbar.BindValueChanged(healthBar =>
+ {
+ if (healthBar.NewValue)
+ {
+ HealthDisplay.FadeIn(fade_duration, fade_easing);
+ topScoreContainer.MoveToY(30, fade_duration, fade_easing);
+ }
+ else
+ {
+ HealthDisplay.FadeOut(fade_duration, fade_easing);
+ topScoreContainer.MoveToY(0, fade_duration, fade_easing);
+ }
+ }, true);
+
+ configShowHud.BindValueChanged(visible =>
+ {
+ if (!ShowHud.Disabled)
+ ShowHud.Value = visible.NewValue;
+ }, true);
+
replayLoaded.BindValueChanged(replayLoadedValueChanged, true);
}
@@ -189,7 +216,7 @@ namespace osu.Game.Screens.Play
switch (e.Key)
{
case Key.Tab:
- showHud.Value = !showHud.Value;
+ configShowHud.Value = !configShowHud.Value;
return true;
}
}
@@ -257,7 +284,7 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding { Top = 20, Right = 10 },
};
- protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.FirstAvailableHitWindows);
+ protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows);
protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index ec524043ee..4acc619753 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -587,13 +587,16 @@ namespace osu.Game.Screens.Select
switch (d)
{
case DrawableCarouselBeatmapSet set:
+ {
lastSet = set;
set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo);
set.MoveToY(currentY, 750, Easing.OutExpo);
break;
+ }
case DrawableCarouselBeatmap beatmap:
+ {
if (beatmap.Item.State.Value == CarouselItemState.Selected)
scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2;
@@ -619,6 +622,7 @@ namespace osu.Game.Screens.Select
}
break;
+ }
}
}
diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs
index 72f3b9ed78..79a4e2e932 100644
--- a/osu.Game/Skinning/LegacySkinResourceStore.cs
+++ b/osu.Game/Skinning/LegacySkinResourceStore.cs
@@ -3,77 +3,39 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Threading.Tasks;
using osu.Framework.IO.Stores;
using osu.Game.Database;
namespace osu.Game.Skinning
{
- public class LegacySkinResourceStore : IResourceStore
+ public class LegacySkinResourceStore : ResourceStore
where T : INamedFileInfo
{
private readonly IHasFiles source;
- private readonly IResourceStore underlyingStore;
-
- private string getPathForFile(string filename)
- {
- if (source.Files == null)
- return null;
-
- bool hasExtension = filename.Contains('.');
-
- var file = source.Files.Find(f =>
- string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase));
- return file?.FileInfo.StoragePath;
- }
public LegacySkinResourceStore(IHasFiles source, IResourceStore underlyingStore)
+ : base(underlyingStore)
{
this.source = source;
- this.underlyingStore = underlyingStore;
}
- public Stream GetStream(string name)
+ protected override IEnumerable GetFilenames(string name)
{
- string path = getPathForFile(name);
- return path == null ? null : underlyingStore.GetStream(path);
- }
+ if (source.Files == null)
+ yield break;
- public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename);
-
- byte[] IResourceStore.Get(string name) => GetAsync(name).Result;
-
- public Task GetAsync(string name)
- {
- string path = getPathForFile(name);
- return path == null ? Task.FromResult(null) : underlyingStore.GetAsync(path);
- }
-
- #region IDisposable Support
-
- private bool isDisposed;
-
- protected virtual void Dispose(bool disposing)
- {
- if (!isDisposed)
+ foreach (var filename in base.GetFilenames(name))
{
- isDisposed = true;
+ var path = getPathForFile(filename);
+ if (path != null)
+ yield return path;
}
}
- ~LegacySkinResourceStore()
- {
- Dispose(false);
- }
+ private string getPathForFile(string filename) =>
+ source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- #endregion
+ public override IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename);
}
}
diff --git a/osu.Game/Tests/Visual/AllPlayersTestScene.cs b/osu.Game/Tests/Visual/AllPlayersTestScene.cs
index b7d1979b0d..dd65c8c382 100644
--- a/osu.Game/Tests/Visual/AllPlayersTestScene.cs
+++ b/osu.Game/Tests/Visual/AllPlayersTestScene.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual
var working = CreateWorkingBeatmap(rulesetInfo);
Beatmap.Value = working;
- Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
+ SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
Player?.Exit();
Player = null;
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 4ca0ec0f7e..18dbd212cc 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -12,7 +12,6 @@ using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Framework.Timing;
@@ -21,6 +20,7 @@ using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
+using osu.Game.Screens;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
@@ -28,21 +28,13 @@ namespace osu.Game.Tests.Visual
{
public abstract class OsuTestScene : TestScene
{
- [Cached(typeof(Bindable))]
- [Cached(typeof(IBindable))]
- private NonNullableBindable beatmap;
+ protected Bindable Beatmap { get; private set; }
- protected Bindable Beatmap => beatmap;
+ protected Bindable Ruleset;
- [Cached]
- [Cached(typeof(IBindable))]
- protected readonly Bindable Ruleset = new Bindable();
+ protected Bindable> SelectedMods;
- [Cached]
- [Cached(Type = typeof(IBindable>))]
- protected readonly Bindable> Mods = new Bindable>(Array.Empty());
-
- protected new DependencyContainer Dependencies { get; private set; }
+ protected new OsuScreenDependencies Dependencies { get; private set; }
private readonly Lazy localStorage;
protected Storage LocalStorage => localStorage.Value;
@@ -72,18 +64,16 @@ namespace osu.Game.Tests.Visual
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures
- var working = new DummyWorkingBeatmap(parent.Get(), parent.Get());
+ Dependencies = new OsuScreenDependencies(false, base.CreateChildDependencies(parent));
- beatmap = new NonNullableBindable(working) { Default = working };
- beatmap.BindValueChanged(b => ScheduleAfterChildren(() =>
- {
- // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
- if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track)
- b.OldValue.RecycleTrack();
- }));
+ Beatmap = Dependencies.Beatmap;
+ Beatmap.SetDefault();
- Dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ Ruleset = Dependencies.Ruleset;
+ Ruleset.SetDefault();
+
+ SelectedMods = Dependencies.Mods;
+ SelectedMods.SetDefault();
if (!UseOnlineAPI)
{
@@ -135,8 +125,8 @@ namespace osu.Game.Tests.Visual
{
base.Dispose(isDisposing);
- if (beatmap?.Value.TrackLoaded == true)
- beatmap.Value.Track.Stop();
+ if (Beatmap?.Value.TrackLoaded == true)
+ Beatmap.Value.Track.Stop();
if (contextFactory.IsValueCreated)
contextFactory.Value.ResetDatabase();
diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs
index 2c5a51ca02..3ed65bee61 100644
--- a/osu.Game/Tests/Visual/PlayerTestScene.cs
+++ b/osu.Game/Tests/Visual/PlayerTestScene.cs
@@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual
{
var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail);
if (noFailMod != null)
- Mods.Value = new[] { noFailMod };
+ SelectedMods.Value = new[] { noFailMod };
}
if (Autoplay)
{
var mod = ruleset.GetAutoplayMod();
if (mod != null)
- Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray();
}
Player = CreatePlayer(ruleset);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index a07348b57c..e5f34b1c7e 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 544bba3963..c84e617285 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -73,8 +73,8 @@
-
-
+
+
@@ -82,7 +82,7 @@
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 9b400de390..12571be31d 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -215,11 +215,12 @@
HINT
HINT
HINT
+ HINT
DO_NOT_SHOW
WARNING
WARNING
WARNING
- DO_NOT_SHOW
+ WARNING
WARNING
True