diff --git a/Gemfile.lock b/Gemfile.lock
index cae682ec2b..07ca3542f9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,25 +3,25 @@ GEM
specs:
CFPropertyList (3.0.5)
rexml
- addressable (2.8.0)
- public_suffix (>= 2.0.2, < 5.0)
+ addressable (2.8.1)
+ public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
- aws-partitions (1.601.0)
- aws-sdk-core (3.131.2)
+ aws-partitions (1.653.0)
+ aws-sdk-core (3.166.0)
aws-eventstream (~> 1, >= 1.0.2)
- aws-partitions (~> 1, >= 1.525.0)
- aws-sigv4 (~> 1.1)
+ aws-partitions (~> 1, >= 1.651.0)
+ aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.57.0)
- aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sdk-kms (1.59.0)
+ aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.114.0)
- aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sdk-s3 (1.117.1)
+ aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
- aws-sigv4 (1.5.0)
+ aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
@@ -34,10 +34,10 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
- dotenv (2.7.6)
+ dotenv (2.8.1)
emoji_regex (3.2.3)
- excon (0.92.3)
- faraday (1.10.0)
+ excon (0.93.1)
+ faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
- fastlane (2.206.2)
+ fastlane (2.210.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -110,9 +110,9 @@ GEM
souyuz (= 0.11.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.23.0)
- google-apis-core (>= 0.6, < 2.a)
- google-apis-core (0.6.0)
+ google-apis-androidpublisher_v3 (0.29.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-apis-core (0.9.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -121,27 +121,27 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
- google-apis-iamcredentials_v1 (0.12.0)
- google-apis-core (>= 0.6, < 2.a)
- google-apis-playcustomapp_v1 (0.9.0)
- google-apis-core (>= 0.6, < 2.a)
- google-apis-storage_v1 (0.16.0)
- google-apis-core (>= 0.6, < 2.a)
+ google-apis-iamcredentials_v1 (0.15.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.12.0)
+ google-apis-core (>= 0.9.1, < 2.a)
+ google-apis-storage_v1 (0.19.0)
+ google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
- google-cloud-errors (1.2.0)
- google-cloud-storage (1.36.2)
+ google-cloud-errors (1.3.0)
+ google-cloud-storage (1.43.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
- google-apis-storage_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
- googleauth (1.2.0)
+ googleauth (1.3.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -154,22 +154,22 @@ GEM
httpclient (2.8.3)
jmespath (1.6.1)
json (2.6.2)
- jwt (2.4.1)
+ jwt (2.5.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
- mini_portile2 (2.7.1)
+ mini_portile2 (2.8.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
- nokogiri (1.13.1)
- mini_portile2 (~> 2.7.0)
+ nokogiri (1.13.9)
+ mini_portile2 (~> 2.8.0)
racc (~> 1.4)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
- public_suffix (4.0.7)
+ public_suffix (5.0.0)
racc (1.6.0)
rake (13.0.6)
representable (3.2.0)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index cc5abf5b03..716115e5c6 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -138,10 +138,10 @@ platform :ios do
end
lane :testflight_prune_dry do
- clean_testflight_testers(days_of_inactivity:45, dry_run: true)
+ clean_testflight_testers(days_of_inactivity:30, dry_run: true)
end
lane :testflight_prune do
- clean_testflight_testers(days_of_inactivity: 45)
+ clean_testflight_testers(days_of_inactivity: 30)
end
end
diff --git a/osu.Android.props b/osu.Android.props
index f251e8ee71..b691751f13 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 3ee1b3da30..09f7292845 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -18,17 +18,9 @@ using osu.Framework;
using osu.Framework.Logging;
using osu.Game.Updater;
using osu.Desktop.Windows;
-using osu.Framework.Input.Handlers;
-using osu.Framework.Input.Handlers.Joystick;
-using osu.Framework.Input.Handlers.Mouse;
-using osu.Framework.Input.Handlers.Tablet;
-using osu.Framework.Input.Handlers.Touch;
using osu.Framework.Threading;
using osu.Game.IO;
using osu.Game.IPC;
-using osu.Game.Overlays.Settings;
-using osu.Game.Overlays.Settings.Sections;
-using osu.Game.Overlays.Settings.Sections.Input;
using osu.Game.Utils;
using SDL2;
@@ -148,27 +140,6 @@ namespace osu.Desktop
desktopWindow.DragDrop += f => fileDrop(new[] { f });
}
- public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
- {
- switch (handler)
- {
- case ITabletHandler th:
- return new TabletSettings(th);
-
- case MouseHandler mh:
- return new MouseSettings(mh);
-
- case JoystickHandler jh:
- return new JoystickSettings(jh);
-
- case TouchHandler th:
- return new InputSection.HandlerSection(th);
-
- default:
- return base.CreateSettingsSubsectionFor(handler);
- }
- }
-
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
private readonly List importableFiles = new List();
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 5c9c95827a..e0f7820262 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Catch.Edit;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Catch.Scoring;
+using osu.Game.Rulesets.Catch.Skinning.Argon;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Difficulty;
@@ -188,6 +189,9 @@ namespace osu.Game.Rulesets.Catch
{
case LegacySkin:
return new CatchLegacySkinTransformer(skin);
+
+ case ArgonSkin:
+ return new CatchArgonSkinTransformer(skin);
}
return null;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
index fd0ffbd032..ddfbb34435 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
@@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public float DisplayRotation => Rotation;
+ public double DisplayStartTime => HitObject.StartTime;
+
///
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
///
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
index 5de372852b..dd09b6c06d 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
+ public double DisplayStartTime => LifetimeStart;
+
Bindable IHasCatchObjectState.AccentColour => AccentColour;
public Bindable HyperDash { get; } = new Bindable();
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
index 93c80b09db..f30ef0831a 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
@@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
PalpableCatchHitObject HitObject { get; }
+ double DisplayStartTime { get; }
+
Bindable AccentColour { get; }
Bindable HyperDash { get; }
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs
new file mode 100644
index 0000000000..9a657c9216
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs
@@ -0,0 +1,122 @@
+// 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.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Objects;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ internal class ArgonBananaPiece : ArgonFruitPiece
+ {
+ private Container stabilisedPieceContainer = null!;
+
+ private Drawable fadeContent = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddInternal(fadeContent = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ stabilisedPieceContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ new Circle
+ {
+ Colour = Color4.White.Opacity(0.4f),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Size = new Vector2(8),
+ Scale = new Vector2(25, 1),
+ },
+ new Box
+ {
+ Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.8f)),
+ RelativeSizeAxes = Axes.X,
+ Blending = BlendingParameters.Additive,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreRight,
+ Width = 1.6f,
+ Height = 2,
+ },
+ new Circle
+ {
+ Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White.Opacity(0)),
+ RelativeSizeAxes = Axes.X,
+ Blending = BlendingParameters.Additive,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreLeft,
+ Width = 1.6f,
+ Height = 2,
+ },
+ }
+ },
+ new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1.2f),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Hollow = false,
+ Colour = Color4.White.Opacity(0.1f),
+ Radius = 50,
+ },
+ Child =
+ {
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ BorderColour = Color4.White.Opacity(0.1f),
+ BorderThickness = 3,
+ },
+ }
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const float parent_scale_application = 0.4f;
+
+ // relative to time on screen
+ const float lens_flare_start = 0.3f;
+ const float lens_flare_end = 0.8f;
+
+ // Undo some of the parent scale being applied to make the lens flare feel a bit better..
+ float scale = parent_scale_application + (1 - parent_scale_application) * (1 / (ObjectState.DisplaySize.X / (CatchHitObject.OBJECT_RADIUS * 2)));
+
+ stabilisedPieceContainer.Rotation = -ObjectState.DisplayRotation;
+ stabilisedPieceContainer.Scale = new Vector2(scale, 1);
+
+ double duration = ObjectState.HitObject.StartTime - ObjectState.DisplayStartTime;
+
+ fadeContent.Alpha = MathHelper.Clamp(
+ Interpolation.ValueAt(
+ Time.Current, 1f, 0f,
+ ObjectState.DisplayStartTime + duration * lens_flare_start,
+ ObjectState.DisplayStartTime + duration * lens_flare_end,
+ Easing.OutQuint
+ ), 0, 1);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
new file mode 100644
index 0000000000..4db0df4a34
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
@@ -0,0 +1,85 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ public class ArgonCatcher : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ Height = 10,
+ Children = new Drawable[]
+ {
+ new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.White,
+ Width = Catcher.ALLOWED_CATCH_RANGE,
+ },
+ new Box
+ {
+ Name = "long line left",
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreRight,
+ Colour = Color4.White,
+ Alpha = 0.25f,
+ RelativeSizeAxes = Axes.X,
+ Width = 20,
+ Height = 1.8f,
+ },
+ new Circle
+ {
+ Name = "bumper left",
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.X,
+ Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
+ Height = 4,
+ },
+ new Box
+ {
+ Name = "long line right",
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreLeft,
+ Colour = Color4.White,
+ Alpha = 0.25f,
+ RelativeSizeAxes = Axes.X,
+ Width = 20,
+ Height = 1.8f,
+ },
+ new Circle
+ {
+ Name = "bumper right",
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.X,
+ Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
+ Height = 4,
+ },
+ }
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs
new file mode 100644
index 0000000000..267f8a06a3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs
@@ -0,0 +1,121 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ internal class ArgonDropletPiece : CatchHitObjectPiece
+ {
+ protected override Drawable HyperBorderPiece => hyperBorderPiece;
+
+ private Drawable hyperBorderPiece = null!;
+
+ private Container layers = null!;
+
+ private float rotationRandomness;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ const float droplet_scale_down = 0.7f;
+
+ int largeBlobSeed = RNG.Next();
+
+ InternalChildren = new[]
+ {
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(20),
+ },
+ layers = new Container
+ {
+ Scale = new Vector2(droplet_scale_down),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.5f,
+ Alpha = 0.15f,
+ Seed = largeBlobSeed
+ },
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.4f,
+ Alpha = 0.5f,
+ Scale = new Vector2(0.7f),
+ Seed = RNG.Next()
+ },
+ }
+ },
+ hyperBorderPiece = new CircularBlob
+ {
+ Scale = new Vector2(droplet_scale_down),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.5f,
+ Alpha = 0.15f,
+ Seed = largeBlobSeed
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ foreach (var sprite in layers)
+ sprite.Colour = colour.NewValue;
+ }, true);
+
+ rotationRandomness = RNG.NextSingle(0.2f, 1);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Note that droplets are rotated at a higher level, so this is mostly just to create more
+ // random arrangements of the multiple layers than actually rotate.
+ //
+ // Because underlying rotation is always clockwise, we apply anti-clockwise resistance to avoid
+ // making things spin too fast.
+ for (int i = 0; i < layers.Count; i++)
+ {
+ layers[i].Rotation -=
+ (float)Clock.ElapsedFrameTime
+ * 0.4f * rotationRandomness
+ // Each layer should alternate rotation speed.
+ * (i % 2 == 1 ? 0.5f : 1);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs
new file mode 100644
index 0000000000..28538d48b3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs
@@ -0,0 +1,121 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ internal class ArgonFruitPiece : CatchHitObjectPiece
+ {
+ protected override Drawable HyperBorderPiece => hyperBorderPiece;
+
+ private Drawable hyperBorderPiece = null!;
+
+ private Container layers = null!;
+
+ private float rotationRandomness;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ int largeBlobSeed = RNG.Next();
+
+ InternalChildren = new[]
+ {
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(20),
+ },
+ layers = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0.15f,
+ InnerRadius = 0.5f,
+ Size = new Vector2(1.1f),
+ Seed = largeBlobSeed,
+ },
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.2f,
+ Alpha = 0.5f,
+ Seed = RNG.Next(),
+ },
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.05f,
+ Seed = RNG.Next(),
+ },
+ }
+ },
+ hyperBorderPiece = new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.08f,
+ Size = new Vector2(1.15f),
+ Seed = largeBlobSeed
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ foreach (var sprite in layers)
+ sprite.Colour = colour.NewValue;
+ }, true);
+
+ rotationRandomness = RNG.NextSingle(0.2f, 1) * (RNG.NextBool() ? -1 : 1);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ for (int i = 0; i < layers.Count; i++)
+ {
+ layers[i].Rotation +=
+ // Layers are ordered from largest to smallest. Smaller layers should rotate more.
+ (i * 2)
+ * (float)Clock.ElapsedFrameTime
+ * 0.02f * rotationRandomness
+ // Each layer should alternate rotation direction.
+ * (i % 2 == 1 ? 1 : -1);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs
new file mode 100644
index 0000000000..90dca49dfd
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs
@@ -0,0 +1,112 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ public class ArgonHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ public override bool RemoveWhenNotAlive => true;
+
+ private Container tallExplosion = null!;
+ private Container largeFaint = null!;
+
+ private readonly Bindable accentColour = new Bindable();
+
+ public ArgonHitExplosion()
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChildren = new Drawable[]
+ {
+ tallExplosion = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Width = 0.1f,
+ Child = new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ },
+ },
+ largeFaint = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Child = new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ },
+ },
+ };
+
+ accentColour.BindValueChanged(colour =>
+ {
+ tallExplosion.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour.NewValue,
+ Hollow = false,
+ Roundness = 15,
+ Radius = 15,
+ };
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.2f, colour.NewValue, Color4.White, 0, 1),
+ Hollow = false,
+ Radius = 50,
+ };
+ }, true);
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ X = entry.Position;
+ Scale = new Vector2(entry.HitObject.Scale);
+ accentColour.Value = entry.ObjectColour;
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ {
+ this.FadeOutFromOne(400);
+
+ if (!(entry.HitObject is Droplet))
+ {
+ float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
+
+ tallExplosion
+ .ScaleTo(new Vector2(1.1f, 20 * scale), 200, Easing.OutQuint)
+ .Then()
+ .ScaleTo(new Vector2(1.1f, 1), 600, Easing.In);
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs
new file mode 100644
index 0000000000..59e8b5a0b3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs
@@ -0,0 +1,193 @@
+// 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.Allocation;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
+ {
+ protected readonly HitResult Result;
+
+ protected SpriteText JudgementText { get; private set; } = null!;
+
+ private RingExplosion? ringExplosion;
+
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+
+ public ArgonJudgementPiece(HitResult result)
+ {
+ Result = result;
+ Origin = Anchor.Centre;
+ Y = 160;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ JudgementText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = Result.GetDescription().ToUpperInvariant(),
+ Colour = colours.ForHitResult(Result),
+ Blending = BlendingParameters.Additive,
+ Spacing = new Vector2(10, 0),
+ Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
+ },
+ };
+
+ if (Result.IsHit())
+ {
+ AddInternal(ringExplosion = new RingExplosion(Result)
+ {
+ Colour = colours.ForHitResult(Result),
+ });
+ }
+ }
+
+ ///
+ /// Plays the default animation for this judgement piece.
+ ///
+ ///
+ /// The base implementation only handles fade (for all result types) and misses.
+ /// Individual rulesets are recommended to implement their appropriate hit animations.
+ ///
+ public virtual void PlayAnimation()
+ {
+ switch (Result)
+ {
+ default:
+ JudgementText
+ .ScaleTo(Vector2.One)
+ .ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
+ break;
+
+ case HitResult.Miss:
+ this.ScaleTo(1.6f);
+ this.ScaleTo(1, 100, Easing.In);
+
+ this.MoveTo(Vector2.Zero);
+ this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
+
+ this.RotateTo(0);
+ this.RotateTo(40, 800, Easing.InQuint);
+ break;
+ }
+
+ this.FadeOutFromOne(800);
+
+ ringExplosion?.PlayAnimation();
+ }
+
+ public Drawable? GetAboveHitObjectsProxiedContent() => null;
+
+ private class RingExplosion : CompositeDrawable
+ {
+ private readonly float travel = 52;
+
+ public RingExplosion(HitResult result)
+ {
+ const float thickness = 4;
+
+ const float small_size = 9;
+ const float large_size = 14;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Blending = BlendingParameters.Additive;
+
+ int countSmall = 0;
+ int countLarge = 0;
+
+ switch (result)
+ {
+ case HitResult.Meh:
+ countSmall = 3;
+ travel *= 0.3f;
+ break;
+
+ case HitResult.Ok:
+ case HitResult.Good:
+ countSmall = 4;
+ travel *= 0.6f;
+ break;
+
+ case HitResult.Great:
+ case HitResult.Perfect:
+ countSmall = 4;
+ countLarge = 4;
+ break;
+ }
+
+ for (int i = 0; i < countSmall; i++)
+ AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
+
+ for (int i = 0; i < countLarge; i++)
+ AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
+ }
+
+ public void PlayAnimation()
+ {
+ foreach (var c in InternalChildren)
+ {
+ const float start_position_ratio = 0.3f;
+
+ float direction = RNG.NextSingle(0, 360);
+ float distance = RNG.NextSingle(travel / 2, travel);
+
+ c.MoveTo(new Vector2(
+ MathF.Cos(direction) * distance * start_position_ratio,
+ MathF.Sin(direction) * distance * start_position_ratio
+ ));
+
+ c.MoveTo(new Vector2(
+ MathF.Cos(direction) * distance,
+ MathF.Sin(direction) * distance
+ ), 600, Easing.OutQuint);
+ }
+
+ this.FadeOutFromOne(1000, Easing.OutQuint);
+ }
+
+ public class RingPiece : CircularContainer
+ {
+ public RingPiece(float thickness = 9)
+ {
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Masking = true;
+ BorderThickness = thickness;
+ BorderColour = Color4.White;
+
+ Child = new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both
+ };
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
new file mode 100644
index 0000000000..8dae0a2b78
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ public class CatchArgonSkinTransformer : SkinTransformer
+ {
+ public CatchArgonSkinTransformer(ISkin skin)
+ : base(skin)
+ {
+ }
+
+ public override Drawable? GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case CatchSkinComponent catchComponent:
+ // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
+ switch (catchComponent.Component)
+ {
+ case CatchSkinComponents.HitExplosion:
+ return new ArgonHitExplosion();
+
+ case CatchSkinComponents.Catcher:
+ return new ArgonCatcher();
+
+ case CatchSkinComponents.Fruit:
+ return new ArgonFruitPiece();
+
+ case CatchSkinComponents.Banana:
+ return new ArgonBananaPiece();
+
+ case CatchSkinComponents.Droplet:
+ return new ArgonDropletPiece();
+ }
+
+ break;
+ }
+
+ return base.GetDrawableComponent(component);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
index 27252594af..359756f159 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
@@ -1,21 +1,19 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class BananaPiece : CatchHitObjectPiece
{
- protected override BorderPiece BorderPiece { get; }
+ protected override Drawable BorderPiece { get; }
public BananaPiece()
{
RelativeSizeAxes = Axes.Both;
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
new BananaPulpFormation
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
index 6cc5220699..3b8df6ee6f 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
@@ -7,6 +7,7 @@ using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osuTK.Graphics;
@@ -26,13 +27,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
/// A part of this piece that will be faded out while falling in the playfield.
///
[CanBeNull]
- protected virtual BorderPiece BorderPiece => null;
+ protected virtual Drawable BorderPiece => null;
///
/// A part of this piece that will be only visible when is true.
///
[CanBeNull]
- protected virtual HyperBorderPiece HyperBorderPiece => null;
+ protected virtual Drawable HyperBorderPiece => null;
protected override void LoadComplete()
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
index 6b7f25eed1..b8ae062382 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
@@ -11,13 +11,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class DropletPiece : CatchHitObjectPiece
{
- protected override HyperBorderPiece HyperBorderPiece { get; }
+ protected override Drawable HyperBorderPiece { get; }
public DropletPiece()
{
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
new Pulp
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
index 8fb5c8f84a..adee960c3c 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
@@ -18,14 +18,14 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
public readonly Bindable VisualRepresentation = new Bindable();
- protected override BorderPiece BorderPiece { get; }
- protected override HyperBorderPiece HyperBorderPiece { get; }
+ protected override Drawable BorderPiece { get; }
+ protected override Drawable HyperBorderPiece { get; }
public FruitPiece()
{
RelativeSizeAxes = Axes.Both;
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
new FruitPulpFormation
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
index bb967a0a76..da2a6ced67 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -40,16 +40,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
body.BorderColour = colours.Yellow;
}
+ private int? lastVersion;
+
public override void UpdateFrom(Slider hitObject)
{
base.UpdateFrom(hitObject);
body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
- var vertices = new List();
- hitObject.Path.GetPathToProgress(vertices, 0, 1);
+ if (lastVersion != hitObject.Path.Version.Value)
+ {
+ lastVersion = hitObject.Path.Version.Value;
- body.SetVertices(vertices);
+ var vertices = new List();
+ hitObject.Path.GetPathToProgress(vertices, 0, 1);
+
+ body.SetVertices(vertices);
+ }
Size = body.Size;
OriginPosition = body.PathOffset;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 265a1d21b1..36ee7c2460 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private readonly BindableList controlPoints = new BindableList();
private readonly IBindable pathVersion = new Bindable();
+ private readonly BindableList selectedObjects = new BindableList();
public SliderSelectionBlueprint(Slider slider)
: base(slider)
@@ -86,6 +87,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
BodyPiece.UpdateFrom(HitObject);
+
+ if (editorBeatmap != null)
+ selectedObjects.BindTo(editorBeatmap.SelectedHitObjects);
+ selectedObjects.BindCollectionChanged((_, _) => updateVisualDefinition(), true);
}
public override bool HandleQuickDeletion()
@@ -100,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return true;
}
+ private bool hasSingleObjectSelected => selectedObjects.Count == 1;
+
protected override void Update()
{
base.Update();
@@ -108,14 +115,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
BodyPiece.UpdateFrom(HitObject);
}
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateVisualDefinition();
+
+ // In the case more than a single object is selected, block hover from arriving at sliders behind this one.
+ // Without doing this, the path visualisers of potentially hundreds of sliders will render, which is not only
+ // visually noisy but also functionally useless.
+ return !hasSingleObjectSelected;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateVisualDefinition();
+ base.OnHoverLost(e);
+ }
+
protected override void OnSelected()
{
- AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
- {
- RemoveControlPointsRequested = removeControlPoints,
- SplitControlPointsRequested = splitControlPoints
- });
-
+ updateVisualDefinition();
base.OnSelected();
}
@@ -123,13 +141,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.OnDeselected();
- // throw away frame buffers on deselection.
- ControlPointVisualiser?.Expire();
- ControlPointVisualiser = null;
-
+ updateVisualDefinition();
BodyPiece.RecyclePath();
}
+ private void updateVisualDefinition()
+ {
+ // To reduce overhead of drawing these blueprints, only add extra detail when hovered or when only this slider is selected.
+ if (IsSelected && (hasSingleObjectSelected || IsHovered))
+ {
+ if (ControlPointVisualiser == null)
+ {
+ AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
+ {
+ RemoveControlPointsRequested = removeControlPoints,
+ SplitControlPointsRequested = splitControlPoints
+ });
+ }
+ }
+ else
+ {
+ ControlPointVisualiser?.Expire();
+ ControlPointVisualiser = null;
+ }
+ }
+
private Vector2 rightClickPosition;
protected override bool OnMouseDown(MouseDownEvent e)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
index 28690ee0b7..b5a13a22ce 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
@@ -5,19 +5,17 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
{
public class SpinnerPiece : BlueprintPiece
{
- private readonly CircularContainer circle;
- private readonly RingPiece ring;
+ private readonly Circle circle;
+ private readonly Circle ring;
public SpinnerPiece()
{
@@ -25,18 +23,21 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
- Size = new Vector2(1.3f);
+ Size = new Vector2(1);
InternalChildren = new Drawable[]
{
- circle = new CircularContainer
+ circle = new Circle
{
RelativeSizeAxes = Axes.Both,
- Masking = true,
Alpha = 0.5f,
- Child = new Box { RelativeSizeAxes = Axes.Both }
},
- ring = new RingPiece()
+ ring = new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS),
+ },
};
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
index de6ca7dd38..9966ad3a90 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
@@ -79,7 +79,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void ApplyTransformsAt(double time, bool propagateChildren = false)
{
// For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either.
- // ReSharper disable once RedundantArgumentDefaultValue - removing the "redundant" default value triggers BaseMethodCallWithDefaultParameter
+
+ // ReSharper disable once RedundantArgumentDefaultValue
base.ApplyTransformsAt(time, false);
}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 524565a863..3cc47deed0 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -57,6 +57,28 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Beatmap converted = base.ConvertBeatmap(original, cancellationToken);
+ if (original.BeatmapInfo.Ruleset.OnlineID == 0)
+ {
+ // Post processing step to transform standard slider velocity changes into scroll speed changes
+ double lastScrollSpeed = 1;
+
+ foreach (HitObject hitObject in original.HitObjects)
+ {
+ double nextScrollSpeed = hitObject.DifficultyControlPoint.SliderVelocity;
+ EffectControlPoint currentEffectPoint = converted.ControlPointInfo.EffectPointAt(hitObject.StartTime);
+
+ if (!Precision.AlmostEquals(lastScrollSpeed, nextScrollSpeed, acceptableDifference: currentEffectPoint.ScrollSpeedBindable.Precision))
+ {
+ converted.ControlPointInfo.Add(hitObject.StartTime, new EffectControlPoint
+ {
+ KiaiMode = currentEffectPoint.KiaiMode,
+ OmitFirstBarLine = currentEffectPoint.OmitFirstBarLine,
+ ScrollSpeed = lastScrollSpeed = nextScrollSpeed,
+ });
+ }
+ }
+ }
+
if (original.BeatmapInfo.Ruleset.OnlineID == 3)
{
// Post processing step to transform mania hit objects with the same start time into strong hits
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index 604b87dc4c..9079ecdc48 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -188,7 +188,7 @@ namespace osu.Game.Tests.Collections.IO
}
// Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null))
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName))
{
try
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index a984f508ea..75510fa822 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -1,17 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Configuration;
+using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
@@ -26,9 +26,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
- private OsuConfigManager localConfig;
+ private OsuConfigManager localConfig = null!;
- private HUDOverlay hudOverlay;
+ private HUDOverlay hudOverlay = null!;
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
@@ -149,6 +149,41 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
}
+ [Test]
+ public void TestHoldForMenuDoesWorkWhenHidden()
+ {
+ bool activated = false;
+
+ HoldForMenuButton getHoldForMenu() => hudOverlay.ChildrenOfType().Single();
+
+ createNew();
+
+ AddStep("bind action", () =>
+ {
+ activated = false;
+
+ var holdForMenu = getHoldForMenu();
+
+ holdForMenu.Action += () => activated = true;
+ });
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+ AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
+
+ AddStep("attempt activate", () =>
+ {
+ InputManager.MoveMouseTo(getHoldForMenu().OfType().Single());
+ InputManager.PressButton(MouseButton.Left);
+ });
+
+ AddUntilStep("activated", () => activated);
+
+ AddStep("release mouse button", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ }
+
[Test]
public void TestInputDoesntWorkWhenHUDHidden()
{
@@ -220,7 +255,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded);
}
- private void createNew(Action action = null)
+ private void createNew(Action? action = null)
{
AddStep("create overlay", () =>
{
@@ -239,7 +274,9 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void Dispose(bool isDisposing)
{
- localConfig?.Dispose();
+ if (localConfig.IsNotNull())
+ localConfig.Dispose();
+
base.Dispose(isDisposing);
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs
index 2ba0fa36c3..90365ec939 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSafeAreaHandling.cs
@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osuTK.Graphics;
@@ -24,6 +25,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private readonly Bindable safeAreaPaddingLeft = new BindableFloat { MinValue = 0, MaxValue = 200 };
private readonly Bindable safeAreaPaddingRight = new BindableFloat { MinValue = 0, MaxValue = 200 };
+ private readonly Bindable applySafeAreaConsiderations = new Bindable(true);
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -84,6 +87,11 @@ namespace osu.Game.Tests.Visual.UserInterface
Current = safeAreaPaddingRight,
LabelText = "Right"
},
+ new SettingsCheckbox
+ {
+ LabelText = "Apply",
+ Current = applySafeAreaConsiderations,
+ },
}
}
}
@@ -93,6 +101,7 @@ namespace osu.Game.Tests.Visual.UserInterface
safeAreaPaddingBottom.BindValueChanged(_ => updateSafeArea());
safeAreaPaddingLeft.BindValueChanged(_ => updateSafeArea());
safeAreaPaddingRight.BindValueChanged(_ => updateSafeArea());
+ applySafeAreaConsiderations.BindValueChanged(_ => updateSafeArea());
});
base.SetUpSteps();
@@ -107,6 +116,8 @@ namespace osu.Game.Tests.Visual.UserInterface
Left = safeAreaPaddingLeft.Value,
Right = safeAreaPaddingRight.Value,
};
+
+ Game.LocalConfig.SetValue(OsuSetting.SafeAreaConsiderations, applySafeAreaConsiderations.Value);
}
[Test]
diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
index d314f40c30..45dffdc94a 100644
--- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
[Test]
public void TestCustomDirectory()
{
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory), null)) // don't use clean run as we are writing a config file.
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file.
{
string osuDesktopStorage = Path.Combine(host.UserStoragePaths.First(), nameof(TestCustomDirectory));
const string custom_tournament = "custom";
@@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
// manual cleaning so we can prepare a config file.
storage.DeleteDirectory(string.Empty);
- using (var storageConfig = new TournamentStorageManager(storage))
+ using (var storageConfig = new TournamentConfigManager(storage))
storageConfig.SetValue(StorageConfig.CurrentTournament, custom_tournament);
try
@@ -66,82 +66,5 @@ namespace osu.Game.Tournament.Tests.NonVisual
}
}
}
-
- [Test]
- public void TestMigration()
- {
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration), null)) // don't use clean run as we are writing test files for migration.
- {
- string osuRoot = Path.Combine(host.UserStoragePaths.First(), nameof(TestMigration));
- string configFile = Path.Combine(osuRoot, "tournament.ini");
-
- if (File.Exists(configFile))
- File.Delete(configFile);
-
- // Recreate the old setup that uses "tournament" as the base path.
- string oldPath = Path.Combine(osuRoot, "tournament");
-
- string videosPath = Path.Combine(oldPath, "Videos");
- string modsPath = Path.Combine(oldPath, "Mods");
- string flagsPath = Path.Combine(oldPath, "Flags");
-
- Directory.CreateDirectory(videosPath);
- Directory.CreateDirectory(modsPath);
- Directory.CreateDirectory(flagsPath);
-
- // Define testing files corresponding to the specific file migrations that are needed
- string bracketFile = Path.Combine(osuRoot, TournamentGameBase.BRACKET_FILENAME);
-
- string drawingsConfig = Path.Combine(osuRoot, "drawings.ini");
- string drawingsFile = Path.Combine(osuRoot, "drawings.txt");
- string drawingsResult = Path.Combine(osuRoot, "drawings_results.txt");
-
- // Define sample files to test recursive copying
- string videoFile = Path.Combine(videosPath, "video.mp4");
- string modFile = Path.Combine(modsPath, "mod.png");
- string flagFile = Path.Combine(flagsPath, "flag.png");
-
- File.WriteAllText(bracketFile, "{}");
- File.WriteAllText(drawingsConfig, "test");
- File.WriteAllText(drawingsFile, "test");
- File.WriteAllText(drawingsResult, "test");
- File.WriteAllText(videoFile, "test");
- File.WriteAllText(modFile, "test");
- File.WriteAllText(flagFile, "test");
-
- try
- {
- var osu = LoadTournament(host);
-
- var storage = osu.Dependencies.Get();
-
- string migratedPath = Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default");
-
- videosPath = Path.Combine(migratedPath, "Videos");
- modsPath = Path.Combine(migratedPath, "Mods");
- flagsPath = Path.Combine(migratedPath, "Flags");
-
- videoFile = Path.Combine(videosPath, "video.mp4");
- modFile = Path.Combine(modsPath, "mod.png");
- flagFile = Path.Combine(flagsPath, "flag.png");
-
- Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath));
-
- Assert.True(storage.Exists(TournamentGameBase.BRACKET_FILENAME));
- Assert.True(storage.Exists("drawings.txt"));
- Assert.True(storage.Exists("drawings_results.txt"));
-
- Assert.True(storage.Exists("drawings.ini"));
-
- Assert.True(storage.Exists(videoFile));
- Assert.True(storage.Exists(modFile));
- Assert.True(storage.Exists(flagFile));
- }
- finally
- {
- host.Exit();
- }
- }
- }
}
}
diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
index 1bbbcc3661..ca6354cb48 100644
--- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
public void CheckIPCLocation()
{
// don't use clean run because files are being written before osu! launches.
- using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation), null))
+ using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation)))
{
string basePath = Path.Combine(host.UserStoragePaths.First(), nameof(CheckIPCLocation));
diff --git a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs b/osu.Game.Tournament/Configuration/TournamentConfigManager.cs
similarity index 55%
rename from osu.Game.Tournament/Configuration/TournamentStorageManager.cs
rename to osu.Game.Tournament/Configuration/TournamentConfigManager.cs
index 0b9a556296..8f256ba9c3 100644
--- a/osu.Game.Tournament/Configuration/TournamentStorageManager.cs
+++ b/osu.Game.Tournament/Configuration/TournamentConfigManager.cs
@@ -8,14 +8,23 @@ using osu.Framework.Platform;
namespace osu.Game.Tournament.Configuration
{
- public class TournamentStorageManager : IniConfigManager
+ public class TournamentConfigManager : IniConfigManager
{
protected override string Filename => "tournament.ini";
- public TournamentStorageManager(Storage storage)
+ private const string default_tournament = "default";
+
+ public TournamentConfigManager(Storage storage)
: base(storage)
{
}
+
+ protected override void InitialiseDefaults()
+ {
+ base.InitialiseDefaults();
+
+ SetDefault(StorageConfig.CurrentTournament, default_tournament);
+ }
}
public enum StorageConfig
diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs
index bd52b6dfed..e59f90a45e 100644
--- a/osu.Game.Tournament/IO/TournamentStorage.cs
+++ b/osu.Game.Tournament/IO/TournamentStorage.cs
@@ -1,10 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
-using System.IO;
using osu.Framework.Bindables;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -13,35 +10,28 @@ using osu.Game.Tournament.Configuration;
namespace osu.Game.Tournament.IO
{
- public class TournamentStorage : MigratableStorage
+ public class TournamentStorage : WrappedStorage
{
- private const string default_tournament = "default";
- private readonly Storage storage;
-
///
/// The storage where all tournaments are located.
///
public readonly Storage AllTournaments;
- private readonly TournamentStorageManager storageConfig;
public readonly Bindable CurrentTournament;
+ protected TournamentConfigManager TournamentConfigManager { get; }
+
public TournamentStorage(Storage storage)
: base(storage.GetStorageForDirectory("tournaments"), string.Empty)
{
- this.storage = storage;
AllTournaments = UnderlyingStorage;
- storageConfig = new TournamentStorageManager(storage);
+ TournamentConfigManager = new TournamentConfigManager(storage);
- if (storage.Exists("tournament.ini"))
- {
- ChangeTargetStorage(AllTournaments.GetStorageForDirectory(storageConfig.Get(StorageConfig.CurrentTournament)));
- }
- else
- Migrate(AllTournaments.GetStorageForDirectory(default_tournament));
+ CurrentTournament = TournamentConfigManager.GetBindable(StorageConfig.CurrentTournament);
+
+ ChangeTargetStorage(AllTournaments.GetStorageForDirectory(CurrentTournament.Value));
- CurrentTournament = storageConfig.GetBindable(StorageConfig.CurrentTournament);
Logger.Log("Using tournament storage: " + GetFullPath(string.Empty));
CurrentTournament.BindValueChanged(updateTournament);
@@ -53,62 +43,6 @@ namespace osu.Game.Tournament.IO
Logger.Log("Changing tournament storage: " + GetFullPath(string.Empty));
}
- protected override void ChangeTargetStorage(Storage newStorage)
- {
- // due to an unfortunate oversight, on OSes that are sensitive to pathname casing
- // the custom flags directory needed to be named `Flags` (uppercase),
- // while custom mods and videos directories needed to be named `mods` and `videos` respectively (lowercase).
- // to unify handling to uppercase, move any non-compliant directories automatically for the user to migrate.
- // can be removed 20220528
- if (newStorage.ExistsDirectory("flags"))
- AttemptOperation(() => Directory.Move(newStorage.GetFullPath("flags"), newStorage.GetFullPath("Flags")));
- if (newStorage.ExistsDirectory("mods"))
- AttemptOperation(() => Directory.Move(newStorage.GetFullPath("mods"), newStorage.GetFullPath("Mods")));
- if (newStorage.ExistsDirectory("videos"))
- AttemptOperation(() => Directory.Move(newStorage.GetFullPath("videos"), newStorage.GetFullPath("Videos")));
-
- base.ChangeTargetStorage(newStorage);
- }
-
public IEnumerable ListTournaments() => AllTournaments.GetDirectories(string.Empty);
-
- public override bool Migrate(Storage newStorage)
- {
- // this migration only happens once on moving to the per-tournament storage system.
- // listed files are those known at that point in time.
- // this can be removed at some point in the future (6 months obsoletion would mean 2021-04-19)
-
- var source = new DirectoryInfo(storage.GetFullPath("tournament"));
- var destination = new DirectoryInfo(newStorage.GetFullPath("."));
-
- if (source.Exists)
- {
- Logger.Log("Migrating tournament assets to default tournament storage.");
- CopyRecursive(source, destination);
- DeleteRecursive(source);
- }
-
- moveFileIfExists(TournamentGameBase.BRACKET_FILENAME, destination);
- moveFileIfExists("drawings.txt", destination);
- moveFileIfExists("drawings_results.txt", destination);
- moveFileIfExists("drawings.ini", destination);
-
- ChangeTargetStorage(newStorage);
- storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament);
- storageConfig.Save();
-
- return true;
- }
-
- private void moveFileIfExists(string file, DirectoryInfo destination)
- {
- if (!storage.Exists(file))
- return;
-
- Logger.Log($"Migrating {file} to default tournament storage.");
- var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file));
- AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true));
- fileInfo.Delete();
- }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 6f9df1ba7f..3208598f56 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -238,14 +238,6 @@ namespace osu.Game.Beatmaps
#region Compatibility properties
- [Ignored]
- [Obsolete("Use BeatmapInfo.Difficulty instead.")] // can be removed 20220719
- public BeatmapDifficulty BaseDifficulty
- {
- get => Difficulty;
- set => Difficulty = value;
- }
-
[Ignored]
public string? Path => File?.Filename;
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index befc56d244..965cc43815 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -340,7 +340,7 @@ namespace osu.Game.Beatmaps
static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo)
{
var metadata = beatmapInfo.Metadata;
- return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename();
+ return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidFilename();
}
}
diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
index d5e96da246..1d9cc0be65 100644
--- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs
+++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
@@ -3,7 +3,6 @@
#nullable disable
-using System;
using System.Collections.Generic;
using osuTK.Graphics;
@@ -22,11 +21,5 @@ namespace osu.Game.Beatmaps.Formats
/// if empty, will fall back to default combo colours.
///
List CustomComboColours { get; }
-
- ///
- /// Adds combo colours to the list.
- ///
- [Obsolete("Use CustomComboColours directly.")] // can be removed 20220215
- void AddComboColours(params Color4[] colours);
}
}
diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs
deleted file mode 100644
index 95c971eebf..0000000000
--- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using System;
-using System.ComponentModel;
-
-namespace osu.Game.Beatmaps.Timing
-{
- [Obsolete("Use osu.Game.Beatmaps.Timing.TimeSignature instead.")]
- public enum TimeSignatures // can be removed 20220722
- {
- [Description("4/4")]
- SimpleQuadruple = 4,
-
- [Description("3/4")]
- SimpleTriple = 3
- }
-}
diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs
deleted file mode 100644
index 0c1b4021a1..0000000000
--- a/osu.Game/Configuration/DatabasedSetting.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using System.ComponentModel.DataAnnotations.Schema;
-using osu.Game.Database;
-
-namespace osu.Game.Configuration
-{
- [Table("Settings")]
- public class DatabasedSetting : IHasPrimaryKey // can be removed 20220315.
- {
- public int ID { get; set; }
-
- public bool IsManaged => ID > 0;
-
- public int? RulesetID { get; set; }
-
- public int? Variant { get; set; }
-
- public int? SkinInfoID { get; set; }
-
- [Column("Key")]
- public string Key { get; set; }
-
- [Column("Value")]
- public string StringValue
- {
- get => Value.ToString();
- set => Value = value;
- }
-
- public object Value;
-
- public DatabasedSetting(string key, object value)
- {
- Key = key;
- Value = value;
- }
-
- ///
- /// Constructor for derived classes that may require serialisation.
- ///
- public DatabasedSetting()
- {
- }
-
- public override string ToString() => $"{Key}=>{Value}";
- }
-}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 1378e1691a..093eaa0f31 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -118,7 +118,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Prefer24HourTime, CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains(@"tt"));
// Gameplay
- SetDefault(OsuSetting.PositionalHitsounds, true); // replaced by level setting below, can be removed 20220703.
SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1);
SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01);
SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
@@ -127,7 +126,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.HitLighting, true);
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
- SetDefault(OsuSetting.ShowProgressGraph, true);
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false);
@@ -154,6 +152,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.SongSelectRightMouseScroll, false);
SetDefault(OsuSetting.Scaling, ScalingMode.Off);
+ SetDefault(OsuSetting.SafeAreaConsiderations, true);
SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f);
SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f);
@@ -203,14 +202,11 @@ namespace osu.Game.Configuration
if (!int.TryParse(pieces[0], out int year)) return;
if (!int.TryParse(pieces[1], out int monthDay)) return;
+ // ReSharper disable once UnusedVariable
int combined = (year * 10000) + monthDay;
- if (combined < 20220103)
- {
- var positionalHitsoundsEnabled = GetBindable(OsuSetting.PositionalHitsounds);
- if (!positionalHitsoundsEnabled.Value)
- SetValue(OsuSetting.PositionalHitsoundsLevel, 0);
- }
+ // migrations can be added here using a condition like:
+ // if (combined < 20220103) { performMigration() }
}
public override TrackedSettings CreateTrackedSettings()
@@ -296,14 +292,11 @@ namespace osu.Game.Configuration
ShowStoryboard,
KeyOverlay,
GameplayLeaderboard,
- PositionalHitsounds,
PositionalHitsoundsLevel,
AlwaysPlayFirstComboBreak,
FloatingComments,
HUDVisibilityMode,
- // This has been migrated to the component itself. can be removed 20221027.
- ShowProgressGraph,
ShowHealthDisplayWhenCantFail,
FadePlayfieldWhenHealthLow,
MouseDisableButtons,
@@ -370,6 +363,7 @@ namespace osu.Game.Configuration
DiscordRichPresence,
AutomaticallyDownloadWhenSpectating,
ShowOnlineExplicitContent,
- LastProcessedMetadataId
+ LastProcessedMetadataId,
+ SafeAreaConsiderations,
}
}
diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs
index d9fdc40abc..16d7441dde 100644
--- a/osu.Game/Database/LegacyExporter.cs
+++ b/osu.Game/Database/LegacyExporter.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Database
/// The item to export.
public void Export(TModel item)
{
- string filename = $"{item.GetDisplayString().GetValidArchiveContentFilename()}{FileExtension}";
+ string filename = $"{item.GetDisplayString().GetValidFilename()}{FileExtension}";
using (var stream = exportStorage.CreateFileSafely(filename))
ExportModelTo(item, stream);
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index edcd020226..1a938c12e5 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -857,17 +857,7 @@ namespace osu.Game.Database
if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0)
{
- legacyCollectionImporter.ImportFromStorage(storage).ContinueWith(task =>
- {
- if (task.Exception != null)
- {
- // can be removed 20221027 (just for initial safety).
- Logger.Error(task.Exception.InnerException, "Collections could not be migrated to realm. Please provide your \"collection.db\" to the dev team.");
- return;
- }
-
- storage.Move("collection.db", "collection.db.migrated");
- });
+ legacyCollectionImporter.ImportFromStorage(storage).ContinueWith(_ => storage.Move("collection.db", "collection.db.migrated"));
}
break;
diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs
index b10071bb45..efb3c4d633 100644
--- a/osu.Game/Extensions/ModelExtensions.cs
+++ b/osu.Game/Extensions/ModelExtensions.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.IO;
-using System.Linq;
+using System.Text.RegularExpressions;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.IO;
@@ -15,6 +15,8 @@ namespace osu.Game.Extensions
{
public static class ModelExtensions
{
+ private static readonly Regex invalid_filename_chars = new Regex(@"(?!$)[^A-Za-z0-9_()[\]. \-]", RegexOptions.Compiled);
+
///
/// Get the relative path in osu! storage for this file.
///
@@ -137,20 +139,14 @@ namespace osu.Game.Extensions
return instance.OnlineID.Equals(other.OnlineID);
}
- private static readonly char[] invalid_filename_characters = Path.GetInvalidFileNameChars()
- // Backslash is added to avoid issues when exporting to zip.
- // See SharpCompress filename normalisation https://github.com/adamhathcock/sharpcompress/blob/a1e7c0068db814c9aa78d86a94ccd1c761af74bd/src/SharpCompress/Writers/Zip/ZipWriter.cs#L143.
- .Append('\\')
- .ToArray();
-
///
- /// Get a valid filename for use inside a zip file. Avoids backslashes being incorrectly converted to directories.
+ /// Create a valid filename which should work across all platforms.
///
- public static string GetValidArchiveContentFilename(this string filename)
- {
- foreach (char c in invalid_filename_characters)
- filename = filename.Replace(c, '_');
- return filename;
- }
+ ///
+ /// This function replaces all characters not included in a very pessimistic list which should be compatible
+ /// across all operating systems. We are using this in place of as
+ /// that function does not have per-platform considerations (and is only made to work on windows).
+ ///
+ public static string GetValidFilename(this string filename) => invalid_filename_chars.Replace(filename, "_");
}
}
diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index 17c51129a7..11e57d4be3 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -29,6 +29,7 @@ namespace osu.Game.Graphics.Containers
private Bindable sizeY;
private Bindable posX;
private Bindable posY;
+ private Bindable applySafeAreaPadding;
private Bindable safeAreaPadding;
@@ -132,6 +133,9 @@ namespace osu.Game.Graphics.Containers
posY = config.GetBindable(OsuSetting.ScalingPositionY);
posY.ValueChanged += _ => Scheduler.AddOnce(updateSize);
+ applySafeAreaPadding = config.GetBindable(OsuSetting.SafeAreaConsiderations);
+ applySafeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize));
+
safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy();
safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize));
}
@@ -192,7 +196,7 @@ namespace osu.Game.Graphics.Containers
bool requiresMasking = targetRect.Size != Vector2.One
// For the top level scaling container, for now we apply masking if safe areas are in use.
// In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas.
- || (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero);
+ || (targetMode == ScalingMode.Everything && (applySafeAreaPadding.Value && safeAreaPadding.Value.Total != Vector2.Zero));
if (requiresMasking)
sizableContainer.Masking = true;
@@ -225,6 +229,9 @@ namespace osu.Game.Graphics.Containers
[Resolved]
private ISafeArea safeArea { get; set; }
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
private readonly bool confineHostCursor;
private readonly LayoutValue cursorRectCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
@@ -259,7 +266,7 @@ namespace osu.Game.Graphics.Containers
{
if (host.Window == null) return;
- bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero;
+ bool coversWholeScreen = Size == Vector2.One && (!config.Get(OsuSetting.SafeAreaConsiderations) || safeArea.SafeAreaPadding.Value.Total == Vector2.Zero);
host.Window.CursorConfineRect = coversWholeScreen ? null : ToScreenSpace(DrawRectangle).AABBFloat;
}
}
diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
index 4469d50acb..77dcfd39e3 100644
--- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
+++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
@@ -44,7 +44,8 @@ namespace osu.Game.Online.API.Requests.Responses
public int MaxCombo { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
- [JsonProperty("rank")]
+ // ScoreRank is aligned to make 0 equal D. We still want to serialise this (even when DefaultValueHandling.Ignore is used).
+ [JsonProperty("rank", DefaultValueHandling = DefaultValueHandling.Include)]
public ScoreRank Rank { get; set; }
[JsonProperty("started_at")]
@@ -153,10 +154,8 @@ namespace osu.Game.Online.API.Requests.Responses
var mods = Mods.Select(apiMod => apiMod.ToMod(rulesetInstance)).ToArray();
- var scoreInfo = ToScoreInfo(mods);
-
+ var scoreInfo = ToScoreInfo(mods, beatmap);
scoreInfo.Ruleset = ruleset;
- if (beatmap != null) scoreInfo.BeatmapInfo = beatmap;
return scoreInfo;
}
@@ -165,25 +164,47 @@ namespace osu.Game.Online.API.Requests.Responses
/// Create a from an API score instance.
///
/// The mod instances, resolved from a ruleset.
- ///
- public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo
+ /// The object to populate the scores' beatmap with.
+ ///
+ /// - If this is a type, then the score will be fully populated with the given object.
+ /// - Otherwise, if this is an type (e.g. ), then only the beatmap ruleset will be populated.
+ /// - Otherwise, if this is null, then the beatmap ruleset will not be populated.
+ /// - The online beatmap ID is populated in all cases.
+ ///
+ ///
+ /// The populated .
+ public ScoreInfo ToScoreInfo(Mod[] mods, IBeatmapInfo? beatmap = null)
{
- OnlineID = OnlineID,
- User = User ?? new APIUser { Id = UserID },
- BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID },
- Ruleset = new RulesetInfo { OnlineID = RulesetID },
- Passed = Passed,
- TotalScore = TotalScore,
- Accuracy = Accuracy,
- MaxCombo = MaxCombo,
- Rank = Rank,
- Statistics = Statistics,
- MaximumStatistics = MaximumStatistics,
- Date = EndedAt,
- Hash = HasReplay ? "online" : string.Empty, // TODO: temporary?
- Mods = mods,
- PP = PP,
- };
+ var score = new ScoreInfo
+ {
+ OnlineID = OnlineID,
+ User = User ?? new APIUser { Id = UserID },
+ BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID },
+ Ruleset = new RulesetInfo { OnlineID = RulesetID },
+ Passed = Passed,
+ TotalScore = TotalScore,
+ Accuracy = Accuracy,
+ MaxCombo = MaxCombo,
+ Rank = Rank,
+ Statistics = Statistics,
+ MaximumStatistics = MaximumStatistics,
+ Date = EndedAt,
+ Hash = HasReplay ? "online" : string.Empty, // TODO: temporary?
+ Mods = mods,
+ PP = PP,
+ };
+
+ if (beatmap is BeatmapInfo realmBeatmap)
+ score.BeatmapInfo = realmBeatmap;
+ else if (beatmap != null)
+ {
+ score.BeatmapInfo.Ruleset.OnlineID = beatmap.Ruleset.OnlineID;
+ score.BeatmapInfo.Ruleset.Name = beatmap.Ruleset.Name;
+ score.BeatmapInfo.Ruleset.ShortName = beatmap.Ruleset.ShortName;
+ }
+
+ return score;
+ }
///
/// Creates a from a local score for score submission.
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 4ceefbf1fd..c1db9630a0 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -182,6 +182,8 @@ namespace osu.Game
private Bindable configRuleset;
+ private Bindable applySafeAreaConsiderations;
+
private Bindable uiScale;
private Bindable configSkin;
@@ -283,10 +285,7 @@ namespace osu.Game
configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset);
uiScale = LocalConfig.GetBindable(OsuSetting.UIScale);
- var preferredRuleset = int.TryParse(configRuleset.Value, out int rulesetId)
- // int parsing can be removed 20220522
- ? RulesetStore.GetRuleset(rulesetId)
- : RulesetStore.GetRuleset(configRuleset.Value);
+ var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value);
try
{
@@ -315,6 +314,9 @@ namespace osu.Game
SelectedMods.BindValueChanged(modsChanged);
Beatmap.BindValueChanged(beatmapChanged, true);
+
+ applySafeAreaConsiderations = LocalConfig.GetBindable(OsuSetting.SafeAreaConsiderations);
+ applySafeAreaConsiderations.BindValueChanged(apply => SafeAreaContainer.SafeAreaOverrideEdges = apply.NewValue ? SafeAreaOverrideEdges : Edges.All);
}
private ExternalLinkOpener externalLinkOpener;
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 7d9ed7bf3e..39ddffd2d0 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -21,7 +21,11 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Input.Handlers;
+using osu.Framework.Input.Handlers.Joystick;
using osu.Framework.Input.Handlers.Midi;
+using osu.Framework.Input.Handlers.Mouse;
+using osu.Framework.Input.Handlers.Tablet;
+using osu.Framework.Input.Handlers.Touch;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -46,6 +50,7 @@ using osu.Game.Online.Spectator;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
+using osu.Game.Overlays.Settings.Sections.Input;
using osu.Game.Resources;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -189,6 +194,8 @@ namespace osu.Game
private RealmAccess realm;
+ protected SafeAreaContainer SafeAreaContainer { get; private set; }
+
///
/// For now, this is used as a source specifically for beat synced components.
/// Going forward, it could potentially be used as the single source-of-truth for beatmap timing.
@@ -341,7 +348,7 @@ namespace osu.Game
GlobalActionContainer globalBindings;
- base.Content.Add(new SafeAreaContainer
+ base.Content.Add(SafeAreaContainer = new SafeAreaContainer
{
SafeAreaOverrideEdges = SafeAreaOverrideEdges,
RelativeSizeAxes = Axes.Both,
@@ -521,6 +528,29 @@ namespace osu.Game
/// Should be overriden per-platform to provide settings for platform-specific handlers.
public virtual SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
{
+ // One would think that this could be moved to the `OsuGameDesktop` class, but doing so means that
+ // OsuGameTestScenes will not show any input options (as they are based on OsuGame not OsuGameDesktop).
+ //
+ // This in turn makes it hard for ruleset creators to adjust input settings while testing their ruleset
+ // within the test browser interface.
+ if (RuntimeInfo.IsDesktop)
+ {
+ switch (handler)
+ {
+ case ITabletHandler th:
+ return new TabletSettings(th);
+
+ case MouseHandler mh:
+ return new MouseSettings(mh);
+
+ case JoystickHandler jh:
+ return new JoystickSettings(jh);
+
+ case TouchHandler:
+ return new InputSection.HandlerSection(handler);
+ }
+ }
+
switch (handler)
{
case MidiHandler:
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 59b56522a4..7f0bded806 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -20,6 +20,7 @@ using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
+using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Settings.Sections.Graphics
@@ -50,6 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private SettingsDropdown resolutionDropdown = null!;
private SettingsDropdown displayDropdown = null!;
private SettingsDropdown windowModeDropdown = null!;
+ private SettingsCheckbox safeAreaConsiderationsCheckbox = null!;
private Bindable scalingPositionX = null!;
private Bindable scalingPositionY = null!;
@@ -101,6 +103,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
ItemSource = resolutions,
Current = sizeFullscreen
},
+ safeAreaConsiderationsCheckbox = new SettingsCheckbox
+ {
+ LabelText = "Shrink game to avoid cameras and notches",
+ Current = osuConfig.GetBindable(OsuSetting.SafeAreaConsiderations),
+ },
new SettingsSlider
{
LabelText = GraphicsSettingsStrings.UIScaling,
@@ -166,7 +173,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModeDropdown.Current.BindValueChanged(_ =>
{
- updateDisplayModeDropdowns();
+ updateDisplaySettingsVisibility();
updateScreenModeWarning();
}, true);
@@ -191,7 +198,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
.Distinct());
}
- updateDisplayModeDropdowns();
+ updateDisplaySettingsVisibility();
}), true);
scalingMode.BindValueChanged(_ =>
@@ -221,11 +228,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
Scheduler.AddOnce(d =>
{
displayDropdown.Items = d;
- updateDisplayModeDropdowns();
+ updateDisplaySettingsVisibility();
}, displays);
}
- private void updateDisplayModeDropdowns()
+ private void updateDisplaySettingsVisibility()
{
if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen)
resolutionDropdown.Show();
@@ -236,6 +243,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
displayDropdown.Show();
else
displayDropdown.Hide();
+
+ if (host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero)
+ safeAreaConsiderationsCheckbox.Show();
+ else
+ safeAreaConsiderationsCheckbox.Hide();
}
private void updateScreenModeWarning()
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index 82fa20aa9c..9d0f43c45a 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -141,6 +141,8 @@ namespace osu.Game.Overlays.Toolbar
Name = "Right buttons",
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
Children = new Drawable[]
{
new Box
diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs
deleted file mode 100644
index 7f926dd8b8..0000000000
--- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using osu.Framework.Extensions.IEnumerableExtensions;
-using osu.Game.Rulesets.Objects.Drawables;
-
-namespace osu.Game.Rulesets.Mods
-{
- [Obsolete(@"Use the singular version IApplicableToDrawableHitObject instead.")] // Can be removed 20211216
- public interface IApplicableToDrawableHitObjects : IApplicableToDrawableHitObject
- {
- void ApplyToDrawableHitObjects(IEnumerable drawables);
-
- void IApplicableToDrawableHitObject.ApplyToDrawableHitObject(DrawableHitObject drawable) => ApplyToDrawableHitObjects(drawable.Yield());
- }
-}
diff --git a/osu.Game/Rulesets/Mods/ICreateReplay.cs b/osu.Game/Rulesets/Mods/ICreateReplay.cs
deleted file mode 100644
index 1e5eeca92c..0000000000
--- a/osu.Game/Rulesets/Mods/ICreateReplay.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Scoring;
-
-namespace osu.Game.Rulesets.Mods
-{
- [Obsolete("Use ICreateReplayData instead")] // Can be removed 20220929
- public interface ICreateReplay : ICreateReplayData
- {
- public Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods);
-
- ModReplayData ICreateReplayData.CreateReplayData(IBeatmap beatmap, IReadOnlyList mods)
- {
- var replayScore = CreateReplayScore(beatmap, mods);
- return new ModReplayData(replayScore.Replay, new ModCreatedUser { Username = replayScore.ScoreInfo.User.Username });
- }
- }
-}
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index e4c91d3037..98df540de4 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -101,9 +101,6 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual bool ValidForMultiplayerAsFreeMod => true;
- [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009
- public virtual bool Ranked => false;
-
///
/// Whether this mod requires configuration to apply changes to the game.
///
diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs
index 6cafe0716d..83afda3a28 100644
--- a/osu.Game/Rulesets/Mods/ModAutoplay.cs
+++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs
@@ -8,7 +8,6 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Replays;
-using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
@@ -33,16 +32,6 @@ namespace osu.Game.Rulesets.Mods
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
- [Obsolete("Override CreateReplayData(IBeatmap, IReadOnlyList) instead")] // Can be removed 20220929
- public virtual Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { Replay = new Replay() };
-
- public virtual ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods)
- {
-#pragma warning disable CS0618
- var replayScore = CreateReplayScore(beatmap, mods);
-#pragma warning restore CS0618
-
- return new ModReplayData(replayScore.Replay, new ModCreatedUser { Username = replayScore.ScoreInfo.User.Username });
- }
+ public virtual ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new Replay(), new ModCreatedUser { Username = @"autoplay" });
}
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index dec68a6c22..d624164013 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -196,18 +196,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
updateState(State.Value, true);
}
- ///
- /// Applies a hit object to be represented by this .
- ///
- [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] // Can be removed 20211021.
- public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
- {
- if (lifetimeEntry != null)
- Apply(lifetimeEntry);
- else
- Apply(hitObject);
- }
-
///
/// Applies a new to be represented by this .
/// A new is automatically created and applied to this .
diff --git a/osu.Game/Screens/Edit/Components/CircularButton.cs b/osu.Game/Screens/Edit/Components/CircularButton.cs
deleted file mode 100644
index 74e4162102..0000000000
--- a/osu.Game/Screens/Edit/Components/CircularButton.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using osu.Game.Graphics.UserInterface;
-using osuTK;
-
-namespace osu.Game.Screens.Edit.Components
-{
- public class CircularButton : OsuButton
- {
- private const float width = 125;
- private const float height = 30;
-
- public CircularButton()
- {
- Size = new Vector2(width, height);
- }
-
- protected override void Update()
- {
- base.Update();
- Content.CornerRadius = DrawHeight / 2f;
- Content.CornerExponent = 2;
- }
- }
-}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
index a73ada76f5..3a93499527 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
@@ -250,7 +250,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private void seekTrackToCurrent()
{
- double target = Current / Content.DrawWidth * editorClock.TrackLength;
+ double target = TimeAtPosition(Current);
editorClock.Seek(Math.Min(editorClock.TrackLength, target));
}
@@ -264,7 +264,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (handlingDragInput)
editorClock.Stop();
- ScrollTo((float)(editorClock.CurrentTime / editorClock.TrackLength) * Content.DrawWidth, false);
+ float position = PositionAtTime(editorClock.CurrentTime);
+ ScrollTo(position, false);
}
protected override bool OnMouseDown(MouseDownEvent e)
diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs
index 30b420441c..45d0cf8462 100644
--- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs
+++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs
@@ -9,7 +9,6 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
-using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD
@@ -45,12 +44,6 @@ namespace osu.Game.Screens.Play.HUD
[Resolved]
private DrawableRuleset? drawableRuleset { get; set; }
- [Resolved]
- private OsuConfigManager config { get; set; } = null!;
-
- [Resolved]
- private SkinManager skinManager { get; set; } = null!;
-
public DefaultSongProgress()
{
RelativeSizeAxes = Axes.X;
@@ -100,47 +93,6 @@ namespace osu.Game.Screens.Play.HUD
{
AllowSeeking.BindValueChanged(_ => updateBarVisibility(), true);
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
-
- migrateSettingFromConfig();
- }
-
- ///
- /// This setting has been migrated to a per-component level.
- /// Only take the value from the config if it is in a non-default state (then reset it to default so it only applies once).
- ///
- /// Can be removed 20221027.
- ///
- private void migrateSettingFromConfig()
- {
- Bindable configShowGraph = config.GetBindable(OsuSetting.ShowProgressGraph);
-
- if (!configShowGraph.IsDefault)
- {
- ShowGraph.Value = configShowGraph.Value;
-
- // This is pretty ugly, but the only way to make this stick...
- var skinnableTarget = this.FindClosestParent();
-
- if (skinnableTarget != null)
- {
- // If the skin is not mutable, a mutable instance will be created, causing this migration logic to run again on the correct skin.
- // Therefore we want to avoid resetting the config value on this invocation.
- if (skinManager.EnsureMutableSkin())
- return;
-
- // If `EnsureMutableSkin` actually changed the skin, default layout may take a frame to apply.
- // See `SkinnableTargetComponentsContainer`'s use of ScheduleAfterChildren.
- ScheduleAfterChildren(() =>
- {
- var skin = skinManager.CurrentSkin.Value;
- skin.UpdateDrawableTarget(skinnableTarget);
-
- skinManager.Save(skin);
- });
-
- configShowGraph.SetDefault();
- }
- }
}
protected override void PopIn()
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
index d6b9c62369..e7b2ce1672 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
@@ -279,6 +279,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
switch (style)
{
case LabelStyles.None:
+ labelEarly.Clear();
+ labelLate.Clear();
break;
case LabelStyles.Icons:
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 7833c2d7fa..2791f5ff8f 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -39,9 +39,16 @@ namespace osu.Game.Screens.Play
///
public float BottomScoringElementsHeight { get; private set; }
- // HUD uses AlwaysVisible on child components so they can be in an updated state for next display.
- // Without blocking input, this would also allow them to be interacted with in such a state.
- public override bool PropagatePositionalInputSubTree => ShowHud.Value;
+ protected override bool ShouldBeConsideredForInput(Drawable child)
+ {
+ // HUD uses AlwaysVisible on child components so they can be in an updated state for next display.
+ // Without blocking input, this would also allow them to be interacted with in such a state.
+ if (ShowHud.Value)
+ return base.ShouldBeConsideredForInput(child);
+
+ // hold to quit button should always be interactive.
+ return child == bottomRightElements;
+ }
public readonly KeyCounterDisplay KeyCounter;
public readonly ModDisplay ModDisplay;
diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs
index e3ac054d1b..5bbd260d3f 100644
--- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs
+++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs
@@ -36,12 +36,6 @@ namespace osu.Game.Screens.Ranking.Statistics
///
public readonly bool RequiresHitEvents;
- [Obsolete("Use constructor which takes creation function instead.")] // Can be removed 20220803.
- public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null)
- : this(name, () => content, true, dimension)
- {
- }
-
///
/// Creates a new , to be displayed inside a in the results screen.
///
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index 4e5d96ccb8..a9f660312e 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -66,8 +66,6 @@ namespace osu.Game.Skinning
}
}
- void IHasComboColours.AddComboColours(params Color4[] colours) => CustomComboColours.AddRange(colours);
-
public Dictionary CustomColours { get; } = new Dictionary();
public readonly Dictionary ConfigDictionary = new Dictionary();
diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs
index 701dcdfc2d..f594a7b2d2 100644
--- a/osu.Game/Skinning/SkinImporter.cs
+++ b/osu.Game/Skinning/SkinImporter.cs
@@ -4,11 +4,9 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
-using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Database;
@@ -33,9 +31,6 @@ namespace osu.Game.Skinning
this.skinResources = skinResources;
modelManager = new ModelManager(storage, realm);
-
- // can be removed 20220420.
- populateMissingHashes();
}
public override IEnumerable HandledExtensions => new[] { ".osk" };
@@ -158,18 +153,6 @@ namespace osu.Game.Skinning
}
modelManager.ReplaceFile(existingFile, stream, realm);
-
- // can be removed 20220502.
- if (!ensureIniWasUpdated(item))
- {
- Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
-
- var existingIni = item.GetFile(@"skin.ini");
- if (existingIni != null)
- item.Files.Remove(existingIni);
-
- writeNewSkinIni();
- }
}
}
@@ -194,38 +177,6 @@ namespace osu.Game.Skinning
}
}
- private bool ensureIniWasUpdated(SkinInfo item)
- {
- // This is a final consistency check to ensure that hash computation doesn't enter an infinite loop.
- // With other changes to the surrounding code this should never be hit, but until we are 101% sure that there
- // are no other cases let's avoid a hard startup crash by bailing and alerting.
-
- var instance = createInstance(item);
-
- return instance.Configuration.SkinInfo.Name == item.Name;
- }
-
- private void populateMissingHashes()
- {
- Realm.Run(realm =>
- {
- var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray();
-
- foreach (SkinInfo skin in skinsWithoutHashes)
- {
- try
- {
- realm.Write(_ => skin.Hash = ComputeHash(skin));
- }
- catch (Exception e)
- {
- modelManager.Delete(skin);
- Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid");
- }
- }
- });
- }
-
private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources);
public void Save(Skin skin)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 22474c0592..9dd0d18817 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -35,7 +35,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index cf70b65578..6dce938ebf 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -62,7 +62,7 @@
-
+
@@ -82,7 +82,7 @@
-
+