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 @@ - +