From 6995b34b08884384528d0a35afa9da56ad7c9985 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 1 Jul 2022 13:14:11 +0200 Subject: [PATCH 01/22] Add Android joystick settings --- osu.Android/AndroidJoystickSettings.cs | 76 ++++++++++++++++++++++++++ osu.Android/OsuGameAndroid.cs | 3 + 2 files changed, 79 insertions(+) create mode 100644 osu.Android/AndroidJoystickSettings.cs diff --git a/osu.Android/AndroidJoystickSettings.cs b/osu.Android/AndroidJoystickSettings.cs new file mode 100644 index 0000000000..26e921a426 --- /dev/null +++ b/osu.Android/AndroidJoystickSettings.cs @@ -0,0 +1,76 @@ +// 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.Android.Input; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; + +namespace osu.Android +{ + public class AndroidJoystickSettings : SettingsSubsection + { + protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad; + + private readonly AndroidJoystickHandler joystickHandler; + + private readonly Bindable enabled = new BindableBool(true); + + private SettingsSlider deadzoneSlider = null!; + + private Bindable handlerDeadzone = null!; + + private Bindable localDeadzone = null!; + + public AndroidJoystickSettings(AndroidJoystickHandler joystickHandler) + { + this.joystickHandler = joystickHandler; + } + + [BackgroundDependencyLoader] + private void load() + { + // use local bindable to avoid changing enabled state of game host's bindable. + handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy(); + localDeadzone = handlerDeadzone.GetUnboundCopy(); + + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = CommonStrings.Enabled, + Current = enabled + }, + deadzoneSlider = new SettingsSlider + { + LabelText = JoystickSettingsStrings.DeadzoneThreshold, + KeyboardStep = 0.01f, + DisplayAsPercentage = true, + Current = localDeadzone, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + enabled.BindTo(joystickHandler.Enabled); + enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true); + + handlerDeadzone.BindValueChanged(val => + { + bool disabled = localDeadzone.Disabled; + + localDeadzone.Disabled = false; + localDeadzone.Value = val.NewValue; + localDeadzone.Disabled = disabled; + }, true); + + localDeadzone.BindValueChanged(val => handlerDeadzone.Value = val.NewValue); + } + } +} diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 636fc7d2df..062f2ce10c 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -96,6 +96,9 @@ namespace osu.Android case AndroidMouseHandler mh: return new AndroidMouseSettings(mh); + case AndroidJoystickHandler jh: + return new AndroidJoystickSettings(jh); + default: return base.CreateSettingsSubsectionFor(handler); } From e6a05ce3e2d8312581dd660ec9518bebee4b230f Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Sun, 3 Jul 2022 13:51:30 -0700 Subject: [PATCH 02/22] Slow down legacy followcircle animations --- .../Objects/Drawables/DrawableSlider.cs | 4 -- .../Objects/Drawables/DrawableSliderBall.cs | 11 ++-- .../Skinning/Default/DefaultFollowCircle.cs | 50 ++++++++++++++ .../Skinning/Default/DefaultSliderBall.cs | 63 ++++++++++++++++-- .../Skinning/Legacy/LegacyFollowCircle.cs | 65 +++++++++++++++++++ 5 files changed, 175 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 91bb7f95f6..d83f5df7a3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -319,13 +319,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables const float fade_out_time = 450; - // intentionally pile on an extra FadeOut to make it happen much faster. - Ball.FadeOut(fade_out_time / 4, Easing.Out); - switch (state) { case ArmedState.Hit: - Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); if (SliderBody?.SnakingOut.Value == true) Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear. break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 7bde60b39d..6bfb4e8aae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour { + public const float FOLLOW_AREA = 2.4f; + public Func GetInitialHitAction; public Color4 AccentColour @@ -31,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables set => ball.Colour = value; } - private Drawable followCircle; private Drawable followCircleReceptor; private DrawableSlider drawableSlider; private Drawable ball; @@ -47,12 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new[] { - followCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) { Origin = Anchor.Centre, Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Alpha = 0, }, followCircleReceptor = new CircularContainer { @@ -103,10 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables tracking = value; - followCircleReceptor.Scale = new Vector2(tracking ? 2.4f : 1f); - - followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); - followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); + followCircleReceptor.Scale = new Vector2(tracking ? FOLLOW_AREA : 1f); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 8211448705..c39a07da4e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,17 +1,25 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultFollowCircle : CompositeDrawable { + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public DefaultFollowCircle() { + Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = new CircularContainer @@ -29,5 +37,47 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } }; } + + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } + } + + private void trackingChanged(ValueChangedEvent tracking) + { + const float scale_duration = 300f; + const float fade_duration = 300f; + + this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint) + .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float fade_time = 450f; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + // intentionally pile on an extra FadeOut to make it happen much faster. + this.FadeOut(fade_time / 4, Easing.Out); + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 47308375e6..3c7dd7ba5d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -1,8 +1,6 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -19,13 +17,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultSliderBall : CompositeDrawable { - private Box box; + private Box box = null!; + + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) + private void load(ISkinSource skin) { - var slider = (DrawableSlider)drawableObject; - RelativeSizeAxes = Axes.Both; float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; @@ -51,10 +50,60 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } }; - slider.Tracking.BindValueChanged(trackingChanged, true); + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } } private void trackingChanged(ValueChangedEvent tracking) => box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float fade_out_time = 450f; + + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) + { + this.ScaleTo(1f) + .FadeIn(); + } + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + // intentionally pile on an extra FadeOut to make it happen much faster + this.FadeOut(fade_out_time / 4, Easing.Out); + + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(1.4f, fade_out_time, Easing.Out); + break; + } + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index b8a559ce07..df33180c7b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,13 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyFollowCircle : CompositeDrawable { + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public LegacyFollowCircle(Drawable animationContent) { // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x @@ -15,8 +23,65 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy animationContent.Anchor = Anchor.Centre; animationContent.Origin = Anchor.Centre; + Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = animationContent; } + + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } + } + + private void trackingChanged(ValueChangedEvent tracking) + { + Debug.Assert(parentObject != null); + + if (parentObject.Judged) + return; + + const float scale_duration = 180f; + const float fade_duration = 90f; + + double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; + double realScaleDuration = scale_duration; + if (tracking.NewValue && maxScaleDuration < realScaleDuration) + realScaleDuration = maxScaleDuration; + double realFadeDuration = fade_duration * realScaleDuration / fade_duration; + + this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, realScaleDuration, Easing.OutQuad) + .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float shrink_duration = 200f; + const float fade_duration = 240f; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.8f, shrink_duration, Easing.OutQuint) + .FadeOut(fade_duration, Easing.InQuint); + } + } } } From 72fb1ae892e71424a45b383b684f791802444fdf Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Tue, 5 Jul 2022 21:04:13 -0700 Subject: [PATCH 03/22] Add forgotten unsubscribes --- .../Skinning/Default/DefaultFollowCircle.cs | 17 ++++++++-- .../Skinning/Default/DefaultSliderBall.cs | 11 +++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 34 ++++++++++++++++--- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index c39a07da4e..7dd45c295d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; @@ -70,14 +72,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; - const float fade_time = 450f; + const float fade_duration = 450f; + // intentionally pile on an extra FadeOut to make it happen much faster using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - // intentionally pile on an extra FadeOut to make it happen much faster. - this.FadeOut(fade_time / 4, Easing.Out); + this.FadeOut(fade_duration / 4, Easing.Out); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 3c7dd7ba5d..37e5e150bd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -73,26 +73,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; - const float fade_out_time = 450f; + const float fade_duration = 450f; using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) { - this.ScaleTo(1f) - .FadeIn(); + this.FadeIn() + .ScaleTo(1f); } using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { // intentionally pile on an extra FadeOut to make it happen much faster - this.FadeOut(fade_out_time / 4, Easing.Out); + this.FadeOut(fade_duration / 4, Easing.Out); switch (state) { case ArmedState.Hit: - this.ScaleTo(1.4f, fade_out_time, Easing.Out); + this.ScaleTo(1.4f, fade_duration, Easing.Out); break; } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index df33180c7b..dc44c86de0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,13 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -23,7 +27,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy animationContent.Anchor = Anchor.Centre; animationContent.Origin = Anchor.Centre; - Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = animationContent; } @@ -44,6 +47,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (parentObject != null) { + parentObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(parentObject); + parentObject.ApplyCustomUpdateState += updateStateTransforms; updateStateTransforms(parentObject, parentObject.State.Value); } @@ -69,18 +75,38 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); } + private void onHitObjectApplied(DrawableHitObject drawableObject) + { + this.ScaleTo(1f) + .FadeOut(); + } + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; const float shrink_duration = 200f; - const float fade_duration = 240f; + const float fade_delay = 175f; + const float fade_duration = 35f; using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.8f, shrink_duration, Easing.OutQuint) - .FadeOut(fade_duration, Easing.InQuint); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) + .Delay(fade_delay) + .FadeOut(fade_duration); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + { + parentObject.HitObjectApplied -= onHitObjectApplied; + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From 84dcd042f42267acae6271996235c76eda670521 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Thu, 7 Jul 2022 20:30:31 -0700 Subject: [PATCH 04/22] Protect duration calculations against unstable fps --- .../Skinning/Default/DefaultFollowCircle.cs | 2 -- .../Skinning/Legacy/LegacyFollowCircle.cs | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 7dd45c295d..8bb36f8c39 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index dc44c86de0..26d3f053ff 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,17 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -67,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; double realScaleDuration = scale_duration; - if (tracking.NewValue && maxScaleDuration < realScaleDuration) + if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) realScaleDuration = maxScaleDuration; double realFadeDuration = fade_duration * realScaleDuration / fade_duration; @@ -77,6 +73,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void onHitObjectApplied(DrawableHitObject drawableObject) { + ClearTransformsAfter(double.MinValue); + this.ScaleTo(1f) .FadeOut(); } From 48911b956af8bbb1835fbd73de06572846b4f2bf Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 10 Jul 2022 17:07:21 -0700 Subject: [PATCH 05/22] Remove ClearTransformsAfter call A bit weird only having one call on its own; probably deserves an entire PR dedicated to adding ClearTransformsAfter calls --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 26d3f053ff..f18c4529ab 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -73,8 +73,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void onHitObjectApplied(DrawableHitObject drawableObject) { - ClearTransformsAfter(double.MinValue); - this.ScaleTo(1f) .FadeOut(); } From 66932f1af6979ddd254231b27a27b981eeef97d3 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 16:52:45 -0700 Subject: [PATCH 06/22] Move shared followcircle code into abstract base class --- .../Skinning/Default/DefaultFollowCircle.cs | 50 +----------- .../Skinning/FollowCircle.cs | 79 +++++++++++++++++++ .../Skinning/Legacy/LegacyFollowCircle.cs | 72 +++-------------- 3 files changed, 92 insertions(+), 109 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 8bb36f8c39..254e220996 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,27 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class DefaultFollowCircle : CompositeDrawable + public class DefaultFollowCircle : FollowCircle { - [Resolved(canBeNull: true)] - private DrawableHitObject? parentObject { get; set; } - public DefaultFollowCircle() { - Alpha = 0f; - RelativeSizeAxes = Axes.Both; - InternalChild = new CircularContainer { RelativeSizeAxes = Axes.Both, @@ -38,28 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } - [BackgroundDependencyLoader] - private void load() - { - if (parentObject != null) - { - var slider = (DrawableSlider)parentObject; - slider.Tracking.BindValueChanged(trackingChanged, true); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (parentObject != null) - { - parentObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(parentObject, parentObject.State.Value); - } - } - - private void trackingChanged(ValueChangedEvent tracking) + protected override void OnTrackingChanged(ValueChangedEvent tracking) { const float scale_duration = 300f; const float fade_duration = 300f; @@ -68,25 +39,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); } - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + protected override void OnSliderEnd() { - // see comment in LegacySliderBall.updateStateTransforms - if (drawableObject is not DrawableSlider) - return; - const float fade_duration = 450f; // intentionally pile on an extra FadeOut to make it happen much faster - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - this.FadeOut(fade_duration / 4, Easing.Out); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (parentObject != null) - parentObject.ApplyCustomUpdateState -= updateStateTransforms; + this.FadeOut(fade_duration / 4, Easing.Out); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs new file mode 100644 index 0000000000..e1e792b89e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -0,0 +1,79 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public abstract class FollowCircle : CompositeDrawable + { + [Resolved(canBeNull: true)] + protected DrawableHitObject? ParentObject { get; private set; } + + public FollowCircle() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + if (ParentObject != null) + { + var slider = (DrawableSlider)ParentObject; + slider.Tracking.BindValueChanged(OnTrackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (ParentObject != null) + { + ParentObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(ParentObject); + + ParentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(ParentObject, ParentObject.State.Value); + } + } + + private void onHitObjectApplied(DrawableHitObject drawableObject) + { + this.ScaleTo(1f) + .FadeOut(); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which may negatively affect performance + if (drawableObject is not DrawableSlider) + return; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderEnd(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (ParentObject != null) + { + ParentObject.HitObjectApplied -= onHitObjectApplied; + ParentObject.ApplyCustomUpdateState -= updateStateTransforms; + } + } + + protected abstract void OnTrackingChanged(ValueChangedEvent tracking); + + protected abstract void OnSliderEnd(); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index f18c4529ab..38e2e74349 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -2,20 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyFollowCircle : CompositeDrawable + public class LegacyFollowCircle : FollowCircle { - [Resolved(canBeNull: true)] - private DrawableHitObject? parentObject { get; set; } - public LegacyFollowCircle(Drawable animationContent) { // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x @@ -27,41 +21,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChild = animationContent; } - [BackgroundDependencyLoader] - private void load() + protected override void OnTrackingChanged(ValueChangedEvent tracking) { - if (parentObject != null) - { - var slider = (DrawableSlider)parentObject; - slider.Tracking.BindValueChanged(trackingChanged, true); - } - } + Debug.Assert(ParentObject != null); - protected override void LoadComplete() - { - base.LoadComplete(); - - if (parentObject != null) - { - parentObject.HitObjectApplied += onHitObjectApplied; - onHitObjectApplied(parentObject); - - parentObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(parentObject, parentObject.State.Value); - } - } - - private void trackingChanged(ValueChangedEvent tracking) - { - Debug.Assert(parentObject != null); - - if (parentObject.Judged) + if (ParentObject.Judged) return; const float scale_duration = 180f; const float fade_duration = 90f; - double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; + double maxScaleDuration = ParentObject.HitStateUpdateTime - Time.Current; double realScaleDuration = scale_duration; if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) realScaleDuration = maxScaleDuration; @@ -71,39 +41,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); } - private void onHitObjectApplied(DrawableHitObject drawableObject) + protected override void OnSliderEnd() { - this.ScaleTo(1f) - .FadeOut(); - } - - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) - { - // see comment in LegacySliderBall.updateStateTransforms - if (drawableObject is not DrawableSlider) - return; - const float shrink_duration = 200f; const float fade_delay = 175f; const float fade_duration = 35f; - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) - .Delay(fade_delay) - .FadeOut(fade_duration); - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (parentObject != null) - { - parentObject.HitObjectApplied -= onHitObjectApplied; - parentObject.ApplyCustomUpdateState -= updateStateTransforms; - } + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) + .Delay(fade_delay) + .FadeOut(fade_duration); } } } From 4453b0b3e831a9619213aa33a42376eea0f43598 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 16:53:51 -0700 Subject: [PATCH 07/22] Replace comment pointer with actual comment --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 37e5e150bd..97bb4a3697 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -73,7 +73,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - // see comment in LegacySliderBall.updateStateTransforms + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which may negatively affect performance if (drawableObject is not DrawableSlider) return; From 1581f1a0ff8a0570d684a2e02bc58417003aeded Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 17:08:52 -0700 Subject: [PATCH 08/22] Convert constructor in abstract class to protected --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index e1e792b89e..943b7d3b4f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [Resolved(canBeNull: true)] protected DrawableHitObject? ParentObject { get; private set; } - public FollowCircle() + protected FollowCircle() { RelativeSizeAxes = Axes.Both; } From eafa11555af3e83c6b79b4503f9904e7306bab7c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 06:41:03 +0300 Subject: [PATCH 09/22] Allow specifying custom search action to metadata sections --- .../Overlays/BeatmapSet/MetadataSection.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs index c6bf94f507..317b369d8f 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -22,11 +23,14 @@ namespace osu.Game.Overlays.BeatmapSet private readonly MetadataType type; private TextFlowContainer textFlow; + private readonly Action searchAction; + private const float transition_duration = 250; - public MetadataSection(MetadataType type) + public MetadataSection(MetadataType type, Action searchAction = null) { this.type = type; + this.searchAction = searchAction; Alpha = 0; @@ -91,7 +95,12 @@ namespace osu.Game.Overlays.BeatmapSet for (int i = 0; i <= tags.Length - 1; i++) { - loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]); + string tag = tags[i]; + + if (searchAction != null) + loaded.AddLink(tag, () => searchAction(tag)); + else + loaded.AddLink(tag, LinkAction.SearchBeatmapSet, tag); if (i != tags.Length - 1) loaded.AddText(" "); @@ -100,7 +109,11 @@ namespace osu.Game.Overlays.BeatmapSet break; case MetadataType.Source: - loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + if (searchAction != null) + loaded.AddLink(text, () => searchAction(text)); + else + loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + break; default: From 97c3eea3aaa939ad9b0d23862a6ab0434bc4bf3f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 06:41:49 +0300 Subject: [PATCH 10/22] Fix beatmap details source and tags not filtering on song select --- osu.Game/Screens/Select/BeatmapDetails.cs | 15 ++++++++++++--- osu.Game/Screens/Select/FilterControl.cs | 21 +++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 2fe62c4a4e..a877dc4863 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -40,6 +40,9 @@ namespace osu.Game.Screens.Select [Resolved] private IAPIProvider api { get; set; } + [Resolved(canBeNull: true)] + private SongSelect songSelect { get; set; } + private IBeatmapInfo beatmapInfo; private APIFailTimes failTimes; @@ -140,9 +143,9 @@ namespace osu.Game.Screens.Select LayoutEasing = Easing.OutQuad, Children = new[] { - description = new MetadataSection(MetadataType.Description), - source = new MetadataSection(MetadataType.Source), - tags = new MetadataSection(MetadataType.Tags), + description = new MetadataSection(MetadataType.Description, searchOnSongSelect), + source = new MetadataSection(MetadataType.Source, searchOnSongSelect), + tags = new MetadataSection(MetadataType.Tags, searchOnSongSelect), }, }, }, @@ -175,6 +178,12 @@ namespace osu.Game.Screens.Select }, loading = new LoadingLayer(true) }; + + void searchOnSongSelect(string text) + { + if (songSelect != null) + songSelect.FilterControl.SearchTextBox.Text = text; + } } private void updateStatistics() diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a92b631100..fe8369ed0c 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select public FilterCriteria CreateCriteria() { - string query = searchTextBox.Text; + string query = SearchTextBox.Text; var criteria = new FilterCriteria { @@ -62,7 +62,8 @@ namespace osu.Game.Screens.Select return criteria; } - private SeekLimitedSearchTextBox searchTextBox; + public SeekLimitedSearchTextBox SearchTextBox { get; private set; } + private CollectionFilterDropdown collectionDropdown; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Select Height = 60, Children = new Drawable[] { - searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, + SearchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, @@ -204,23 +205,23 @@ namespace osu.Game.Screens.Select updateCriteria(); }; - searchTextBox.Current.ValueChanged += _ => updateCriteria(); + SearchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); } public void Deactivate() { - searchTextBox.ReadOnly = true; - searchTextBox.HoldFocus = false; - if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(searchTextBox); + SearchTextBox.ReadOnly = true; + SearchTextBox.HoldFocus = false; + if (SearchTextBox.HasFocus) + GetContainingInputManager().ChangeFocus(SearchTextBox); } public void Activate() { - searchTextBox.ReadOnly = false; - searchTextBox.HoldFocus = true; + SearchTextBox.ReadOnly = false; + SearchTextBox.HoldFocus = true; } private readonly IBindable ruleset = new Bindable(); From 86d019c2b236e29dbba86f1db034837f0e28bea1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 06:52:06 +0300 Subject: [PATCH 11/22] Enable NRT on `BeatmapDetails` --- osu.Game/Screens/Select/BeatmapDetails.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a877dc4863..80721d14ae 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -1,8 +1,6 @@ // 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.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -38,18 +36,18 @@ namespace osu.Game.Screens.Select private readonly LoadingLayer loading; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - [Resolved(canBeNull: true)] - private SongSelect songSelect { get; set; } + [Resolved] + private SongSelect? songSelect { get; set; } - private IBeatmapInfo beatmapInfo; + private IBeatmapInfo? beatmapInfo; - private APIFailTimes failTimes; + private APIFailTimes? failTimes; - private int[] ratings; + private int[]? ratings; - public IBeatmapInfo BeatmapInfo + public IBeatmapInfo? BeatmapInfo { get => beatmapInfo; set @@ -59,7 +57,7 @@ namespace osu.Game.Screens.Select beatmapInfo = value; var onlineInfo = beatmapInfo as IBeatmapOnlineInfo; - var onlineSetInfo = beatmapInfo.BeatmapSet as IBeatmapSetOnlineInfo; + var onlineSetInfo = beatmapInfo?.BeatmapSet as IBeatmapSetOnlineInfo; failTimes = onlineInfo?.FailTimes; ratings = onlineSetInfo?.Ratings; From 966882013df529b3662f8b594a7fe7dccd240906 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 15:47:05 +0900 Subject: [PATCH 12/22] Remove classic mod attribution to `SoloScoreInfo` conversion path --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index b70da194a5..d139718a3c 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -103,9 +103,6 @@ namespace osu.Game.Online.API.Requests.Responses var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); - // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); - var scoreInfo = ToScoreInfo(mods); scoreInfo.Ruleset = ruleset; From 688fcb256f41e5ad0fe96ee3fe2b9d2f82b5b960 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 15:47:25 +0900 Subject: [PATCH 13/22] Update score retrieval endpoint to access new storage --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index a6cd9a52c7..966e69938c 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online.API.Requests this.mods = mods ?? Array.Empty(); } - protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}"; + protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/solo-scores{createQueryParameters()}"; private string createQueryParameters() { From 6122d2a52553a473f8a64fbb09bc78081d1740a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 15:58:52 +0900 Subject: [PATCH 14/22] Add "F" `ScoreRank` to handle old scores which have this specified Not sure on the future of this, but given it is used in the save-failed-reply pull request (#18785) I think it's fine to add back for now. Without this, JSON parsing of older scores in server-side storage will fail on missing enum type. --- osu.Game/Scoring/ScoreRank.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 9de8561b4a..a1916953c4 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -11,6 +11,10 @@ namespace osu.Game.Scoring { public enum ScoreRank { + // TODO: Localisable? + [Description(@"F")] + F = -1, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.RankD))] [Description(@"D")] D, From c8c79d2185d38b2cd440ed79e2448804ca74bd07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 16:11:11 +0900 Subject: [PATCH 15/22] Standardise `HasReplay` implementation (and remove from persisting to realm) --- osu.Game/Database/EFToRealmMigrator.cs | 1 - osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +-- osu.Game/Scoring/ScoreInfo.cs | 2 +- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 3b5424b3fb..294a8cd3ed 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -443,7 +443,6 @@ namespace osu.Game.Database TotalScore = score.TotalScore, MaxCombo = score.MaxCombo, Accuracy = score.Accuracy, - HasReplay = ((IScoreInfo)score).HasReplay, Date = score.Date, PP = score.PP, Rank = score.Rank, diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 02b5a51f1f..bebc101b13 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -59,8 +59,9 @@ namespace osu.Game.Database /// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields). /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. + /// 16 2022-07-15 Removed HasReplay from ScoreInfo. /// - private const int schema_version = 15; + private const int schema_version = 16; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index d139718a3c..6c48c7883f 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -129,8 +129,7 @@ namespace osu.Game.Online.API.Requests.Responses Rank = Rank, Statistics = Statistics, Date = EndedAt ?? DateTimeOffset.Now, - Hash = "online", // TODO: temporary? - HasReplay = HasReplay, + Hash = HasReplay ? "online" : string.Empty, // TODO: temporary? Mods = mods, PP = PP, }; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index cbe9615380..81ca5f0300 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -45,7 +45,7 @@ namespace osu.Game.Scoring public double Accuracy { get; set; } - public bool HasReplay { get; set; } + public bool HasReplay => !string.IsNullOrEmpty(Hash); public DateTimeOffset Date { get; set; } diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 954a5159b9..7081a0156e 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Ranking if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (!string.IsNullOrEmpty(Score.Value?.Hash)) + if (Score.Value?.HasReplay == true) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable; From 0ade8db550cf193b7093d5d42658f690bbba2a38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 16:40:48 +0900 Subject: [PATCH 16/22] Tidy up nullability and casting --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 943b7d3b4f..321705d25e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { public abstract class FollowCircle : CompositeDrawable { - [Resolved(canBeNull: true)] + [Resolved] protected DrawableHitObject? ParentObject { get; private set; } protected FollowCircle() @@ -23,11 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load() { - if (ParentObject != null) - { - var slider = (DrawableSlider)ParentObject; - slider.Tracking.BindValueChanged(OnTrackingChanged, true); - } + ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true); } protected override void LoadComplete() From 7ed4eb5815cc682567da35177f5d7f774a406cad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:01:01 +0900 Subject: [PATCH 17/22] Adjust transform logic to match osu-stable (and add TODOs for remaining oversights) --- .../Skinning/Legacy/LegacyFollowCircle.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 38e2e74349..324f2525bc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -28,28 +28,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (ParentObject.Judged) return; - const float scale_duration = 180f; - const float fade_duration = 90f; + double remainingTime = ParentObject.HitStateUpdateTime - Time.Current; - double maxScaleDuration = ParentObject.HitStateUpdateTime - Time.Current; - double realScaleDuration = scale_duration; - if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) - realScaleDuration = maxScaleDuration; - double realFadeDuration = fade_duration * realScaleDuration / fade_duration; - - this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, realScaleDuration, Easing.OutQuad) - .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); + // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. + // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this). + if (tracking.NewValue) + { + // TODO: Follow circle should bounce on each slider tick. + this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) + .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); + } + else + { + // TODO: Should animate only at the next slider tick if we want to match stable perfectly. + this.ScaleTo(4f, 100) + .FadeTo(0f, 100); + } } protected override void OnSliderEnd() { - const float shrink_duration = 200f; - const float fade_delay = 175f; - const float fade_duration = 35f; - - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) - .Delay(fade_delay) - .FadeOut(fade_duration); + this.ScaleTo(1.6f, 200, Easing.Out) + .FadeOut(200, Easing.In); } } } From 4b253f83c3ca9c1795494ae2b6e48ce2b4ef2b32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:39:08 +0900 Subject: [PATCH 18/22] Fix intermittent test failures due to randomised score statistics --- .../Visual/Online/TestSceneScoresContainer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index be03328caa..6ef87f762c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -274,14 +274,18 @@ namespace osu.Game.Tests.Visual.Online } }; + const int initial_great_count = 2000; + + int greatCount = initial_great_count; + foreach (var s in scores.Scores) { s.Statistics = new Dictionary { - { HitResult.Great, RNG.Next(2000) }, - { HitResult.Ok, RNG.Next(2000) }, - { HitResult.Meh, RNG.Next(2000) }, - { HitResult.Miss, RNG.Next(2000) } + { HitResult.Great, greatCount -= 100 }, + { HitResult.Ok, RNG.Next(100) }, + { HitResult.Meh, RNG.Next(100) }, + { HitResult.Miss, initial_great_count - greatCount } }; } From 105ffdbbdd200991de8a0961db6431dd90272578 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:57:14 +0900 Subject: [PATCH 19/22] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index caaa83bff4..d5390e6a3d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 355fd5f458..61fcf2e375 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index fcf71f3ab0..8843b5c831 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From e12e4803939cd3058bffac6c83262fc8916aa352 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 18:01:35 +0900 Subject: [PATCH 20/22] Only expose bindable string rather than full textbox --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 80721d14ae..90418efe15 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Select void searchOnSongSelect(string text) { if (songSelect != null) - songSelect.FilterControl.SearchTextBox.Text = text; + songSelect.FilterControl.CurrentTextSearch.Value = text; } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index fe8369ed0c..e43261f374 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Select public Action FilterChanged; + public Bindable CurrentTextSearch => searchTextBox.Current; + private OsuTabControl sortTabs; private Bindable sortMode; @@ -39,7 +41,7 @@ namespace osu.Game.Screens.Select public FilterCriteria CreateCriteria() { - string query = SearchTextBox.Text; + string query = searchTextBox.Text; var criteria = new FilterCriteria { @@ -62,7 +64,7 @@ namespace osu.Game.Screens.Select return criteria; } - public SeekLimitedSearchTextBox SearchTextBox { get; private set; } + private SeekLimitedSearchTextBox searchTextBox; private CollectionFilterDropdown collectionDropdown; @@ -104,7 +106,7 @@ namespace osu.Game.Screens.Select Height = 60, Children = new Drawable[] { - SearchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, + searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, new Box { RelativeSizeAxes = Axes.X, @@ -205,23 +207,23 @@ namespace osu.Game.Screens.Select updateCriteria(); }; - SearchTextBox.Current.ValueChanged += _ => updateCriteria(); + searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); } public void Deactivate() { - SearchTextBox.ReadOnly = true; - SearchTextBox.HoldFocus = false; - if (SearchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(SearchTextBox); + searchTextBox.ReadOnly = true; + searchTextBox.HoldFocus = false; + if (searchTextBox.HasFocus) + GetContainingInputManager().ChangeFocus(searchTextBox); } public void Activate() { - SearchTextBox.ReadOnly = false; - SearchTextBox.HoldFocus = true; + searchTextBox.ReadOnly = false; + searchTextBox.HoldFocus = true; } private readonly IBindable ruleset = new Bindable(); From cb1d60cd3634234a1ceef318f70ab29266a5b9dc Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 15 Jul 2022 11:57:05 +0200 Subject: [PATCH 21/22] Include new file in compile --- osu.Android/osu.Android.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 90b02c527b..004cc8c39c 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -26,6 +26,7 @@ true + From 775c6c83748df35e1ac23869124f461589b31ea6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 19:29:37 +0900 Subject: [PATCH 22/22] Fix potential crash in editor from transform time going below zero --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 324f2525bc..5b7da5a1ba 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (ParentObject.Judged) return; - double remainingTime = ParentObject.HitStateUpdateTime - Time.Current; + double remainingTime = Math.Max(0, ParentObject.HitStateUpdateTime - Time.Current); // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).