From e3ba7d9ba56c334b2fe815ca1d15e59539bd9f65 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 10:24:40 +0100 Subject: [PATCH 01/34] Wiggle mod expansion Free dlc! --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index ff6ba6e121..e0b09213b7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -22,8 +24,21 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; - private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles - private const int wiggle_strength = 10; // Higher = stronger wiggles + [SettingSource("Wiggle strength", "something")] + public BindableDouble WiggleStrength { get; } = new BindableDouble(10) + { + MinValue = 1f, + MaxValue = 15f, + Precision = .5f + }; + + [SettingSource("Wiggle duration", "milliseconds per wiggle")] + public BindableInt WiggleDuration { get; } = new BindableInt(90) + { + MinValue = 40, + MaxValue = 300, + Precision = 5 + }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -42,18 +57,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration; + int amountWiggles = (int)osuObject.TimePreempt / WiggleDuration.Value; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * wiggle_strength); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); + float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * WiggleDuration.Value)) wiggle(); } @@ -61,11 +76,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / wiggle_duration); + amountWiggles = (int)(endTime.Duration / WiggleDuration.Value); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * WiggleDuration.Value)) wiggle(); } } From a427e20090fc6ea3a3161f1f90bfa3df35bcf9ef Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 10:38:11 +0100 Subject: [PATCH 02/34] Fixes --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index e0b09213b7..7116ee819c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; - [SettingSource("Wiggle strength", "something")] + [SettingSource("Strength")] public BindableDouble WiggleStrength { get; } = new BindableDouble(10) { MinValue = 1f, @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = .5f }; - [SettingSource("Wiggle duration", "milliseconds per wiggle")] + [SettingSource("Duration", "Milliseconds per wiggle")] public BindableInt WiggleDuration { get; } = new BindableInt(90) { MinValue = 40, From 836cb1ee323416b71d2f649bfd725d098fa8dad9 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 12:16:29 +0100 Subject: [PATCH 03/34] Suggested boundary change --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 7116ee819c..10806318b0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; [SettingSource("Strength")] - public BindableDouble WiggleStrength { get; } = new BindableDouble(10) + public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { - MinValue = 1f, - MaxValue = 15f, - Precision = .5f + MinValue = .1f, + MaxValue = 2f, + Precision = .1f }; [SettingSource("Duration", "Milliseconds per wiggle")] @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value); + float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); } From 35be0f24d02a5474b05b997526f662021311bb4d Mon Sep 17 00:00:00 2001 From: mk-56 Date: Thu, 27 Jan 2022 00:10:15 +0100 Subject: [PATCH 04/34] fixed leading "0"s not being present infront of decimal floats --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 10806318b0..853c974a04 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Strength")] public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { - MinValue = .1f, + MinValue = 0.1f, MaxValue = 2f, - Precision = .1f + Precision = 0.1f }; [SettingSource("Duration", "Milliseconds per wiggle")] From dd8fc710fafc1319bb16613f056b3cf9285d4308 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Fri, 4 Feb 2022 15:48:46 +0100 Subject: [PATCH 05/34] removed wiggle duration --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 853c974a04..fae9d785fb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -31,15 +31,6 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 2f, Precision = 0.1f }; - - [SettingSource("Duration", "Milliseconds per wiggle")] - public BindableInt WiggleDuration { get; } = new BindableInt(90) - { - MinValue = 40, - MaxValue = 300, - Precision = 5 - }; - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -57,18 +48,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / WiggleDuration.Value; + int amountWiggles = (int)osuObject.TimePreempt / 70; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), 70); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * WiggleDuration.Value)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * 70)) wiggle(); } @@ -76,11 +67,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / WiggleDuration.Value); + amountWiggles = (int)(endTime.Duration / 70); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * WiggleDuration.Value)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * 70)) wiggle(); } } From d213f56f79001d79267940372a472238a58d7df8 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 19 Jul 2022 02:08:53 -0700 Subject: [PATCH 06/34] Align legacy followcircle anims to slider ticks --- .../Skinning/Default/DefaultFollowCircle.cs | 42 ++++++-------- .../Skinning/FollowCircle.cs | 31 +++++++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 39 +++++++------ .../Skinning/TickFollowCircle.cs | 58 +++++++++++++++++++ 4 files changed, 119 insertions(+), 51 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index b77d4addee..0acd1a56b6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.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. -using System.Diagnostics; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -32,37 +30,31 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } - protected override void OnTrackingChanged(ValueChangedEvent tracking) + protected override void OnSliderPress() { - Debug.Assert(ParentObject != null); - const float duration = 300f; - if (ParentObject.Judged) - return; + if (Precision.AlmostEquals(0, Alpha)) + this.ScaleTo(1); - if (tracking.NewValue) - { - if (Precision.AlmostEquals(0, Alpha)) - this.ScaleTo(1); - - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) - .FadeTo(1f, duration, Easing.OutQuint); - } - else - { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration / 2, Easing.OutQuint) - .FadeTo(0, duration / 2, Easing.OutQuint); - } + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) + .FadeIn(duration, Easing.OutQuint); } - protected override void OnSliderEnd() + protected override void OnSliderRelease() { - const float fade_duration = 300; + const float duration = 150; - // intentionally pile on an extra FadeOut to make it happen much faster - this.ScaleTo(1, fade_duration, Easing.OutQuint); - this.FadeOut(fade_duration / 2, Easing.OutQuint); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint) + .FadeTo(0, duration, Easing.OutQuint); + } + + protected override void OnSliderTail() + { + const float duration = 300; + + this.ScaleTo(1, duration, Easing.OutQuint) + .FadeOut(duration / 2, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 321705d25e..ca903b678d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -1,8 +1,8 @@ // 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; @@ -23,7 +23,17 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load() { - ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true); + ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(tracking => + { + Debug.Assert(ParentObject != null); + if (ParentObject.Judged) + return; + + if (tracking.NewValue) + OnSliderPress(); + else + OnSliderRelease(); + }, true); } protected override void LoadComplete() @@ -48,13 +58,18 @@ namespace osu.Game.Rulesets.Osu.Skinning 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(); + { + switch (state) + { + case ArmedState.Hit: + OnSliderTail(); + break; + } + } } protected override void Dispose(bool isDisposing) @@ -68,8 +83,10 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - protected abstract void OnTrackingChanged(ValueChangedEvent tracking); + protected abstract void OnSliderPress(); - protected abstract void OnSliderEnd(); + protected abstract void OnSliderRelease(); + + protected abstract void OnSliderTail(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 5b7da5a1ba..965f2a9cbe 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -3,12 +3,11 @@ using System; using System.Diagnostics; -using osu.Framework.Bindables; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyFollowCircle : FollowCircle + public class LegacyFollowCircle : TickFollowCircle { public LegacyFollowCircle(Drawable animationContent) { @@ -21,35 +20,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChild = animationContent; } - protected override void OnTrackingChanged(ValueChangedEvent tracking) + protected override void OnSliderPress() { Debug.Assert(ParentObject != null); - if (ParentObject.Judged) - return; - 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). - 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); - } + this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) + .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); } - protected override void OnSliderEnd() + protected override void OnSliderTail() { this.ScaleTo(1.6f, 200, Easing.Out) .FadeOut(200, Easing.In); } + + protected override void OnSliderTick() + { + // TODO: Follow circle should bounce on each slider tick. + + // TEMP DUMMY ANIMS + this.ScaleTo(2.2f) + .ScaleTo(2f, 175f); + } + + protected override void OnSliderBreak() + { + this.ScaleTo(4f, 100) + .FadeTo(0f, 100); + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs new file mode 100644 index 0000000000..39d62064ab --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public abstract class TickFollowCircle : FollowCircle + { + protected override void LoadComplete() + { + base.LoadComplete(); + + if (ParentObject != null) + ParentObject.ApplyCustomUpdateState += updateStateTransforms; + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + switch (state) + { + case ArmedState.Hit: + if (drawableObject is DrawableSliderTick or DrawableSliderRepeat) + OnSliderTick(); + break; + + case ArmedState.Miss: + if (drawableObject is DrawableSlider or DrawableSliderTick or DrawableSliderRepeat) + OnSliderBreak(); + break; + } + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (ParentObject != null) + ParentObject.ApplyCustomUpdateState -= updateStateTransforms; + } + + /// + /// Sealed empty. Override instead, since animations + /// should only play on slider ticks. + /// + protected sealed override void OnSliderRelease() + { + } + + protected abstract void OnSliderTick(); + + protected abstract void OnSliderBreak(); + } +} From e1f7db6e7df40777fb743e90e50cf51251c301aa Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 19 Jul 2022 02:25:14 -0700 Subject: [PATCH 07/34] Fix around some comments --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 3 +++ osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index ca903b678d..ec9d188c6b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -58,6 +58,9 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // We only want DrawableSlider here. DrawableSliderTail doesn't quite work because its + // HitStateUpdateTime is ~36ms before DrawableSlider's HitStateUpdateTime (aka slider + // end leniency). if (drawableObject is not DrawableSlider) return; diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs index 39d62064ab..3aeb0c7c0a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -44,8 +44,8 @@ namespace osu.Game.Rulesets.Osu.Skinning } /// - /// Sealed empty. Override instead, since animations - /// should only play on slider ticks. + /// Sealed empty intentionally. Override instead, since + /// animations should only play on slider ticks. /// protected sealed override void OnSliderRelease() { From 5cb0920cfbdd18da5b3254f27bdbeb94dd281880 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 19 Jul 2022 02:27:04 -0700 Subject: [PATCH 08/34] Revert OnSliderTail() to OnSliderEnd() In light of the comment added in the previous commit, slider tail and end are not actually the same. --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 4 ++-- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 0acd1a56b6..51cfb2568b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default .FadeTo(0, duration, Easing.OutQuint); } - protected override void OnSliderTail() + protected override void OnSliderEnd() { const float duration = 300; diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index ec9d188c6b..d7685c1724 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (state) { case ArmedState.Hit: - OnSliderTail(); + OnSliderEnd(); break; } } @@ -90,6 +90,6 @@ namespace osu.Game.Rulesets.Osu.Skinning protected abstract void OnSliderRelease(); - protected abstract void OnSliderTail(); + 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 965f2a9cbe..9e1c2e7e9d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); } - protected override void OnSliderTail() + protected override void OnSliderEnd() { this.ScaleTo(1.6f, 200, Easing.Out) .FadeOut(200, Easing.In); From 23fd514ca3b691c6e6043288c8d49b2204030477 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Wed, 20 Jul 2022 18:07:02 -0700 Subject: [PATCH 09/34] Use DrawableSliderTail instead of DrawableSlider --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 11 ++++++----- osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index d7685c1724..06fa381cbb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -58,13 +58,14 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - // We only want DrawableSlider here. DrawableSliderTail doesn't quite work because its - // HitStateUpdateTime is ~36ms before DrawableSlider's HitStateUpdateTime (aka slider - // end leniency). - if (drawableObject is not DrawableSlider) + if (drawableObject is not DrawableSliderTail) return; - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + Debug.Assert(ParentObject != null); + + // Use ParentObject instead of drawableObject because slider tail hit state update time + // is ~36ms before the actual slider end (aka slider tail leniency) + using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime)) { switch (state) { diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs index 3aeb0c7c0a..de8a8150d0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // Fine to use drawableObject.HitStateUpdateTime even for DrawableSliderTail, since on + // stable, the break anim plays right when the tail is missed, not when the slider ends using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { switch (state) @@ -28,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case ArmedState.Miss: - if (drawableObject is DrawableSlider or DrawableSliderTick or DrawableSliderRepeat) + if (drawableObject is DrawableSliderTail or DrawableSliderTick or DrawableSliderRepeat) OnSliderBreak(); break; } From 726042d9ec2d8eef2a3549fe665d69ba8c51cc15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:16:28 +0900 Subject: [PATCH 10/34] Use switch instead of `or` --- .../Skinning/TickFollowCircle.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs index de8a8150d0..ca1959a561 100644 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -25,13 +25,26 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (state) { case ArmedState.Hit: - if (drawableObject is DrawableSliderTick or DrawableSliderRepeat) - OnSliderTick(); + switch (drawableObject) + { + case DrawableSliderTick: + case DrawableSliderRepeat: + OnSliderTick(); + break; + } + break; case ArmedState.Miss: - if (drawableObject is DrawableSliderTail or DrawableSliderTick or DrawableSliderRepeat) - OnSliderBreak(); + switch (drawableObject) + { + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + OnSliderBreak(); + break; + } + break; } } From c2c2c505a4f2ab578afaff235e3c042e1e42bf00 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 21 Jul 2022 19:46:46 -0700 Subject: [PATCH 11/34] Combine FollowCircle and TickFollowCircle classes --- .../Skinning/Default/DefaultFollowCircle.cs | 13 ++++ .../Skinning/FollowCircle.cs | 52 ++++++++++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 6 +- .../Skinning/TickFollowCircle.cs | 73 ------------------- 4 files changed, 58 insertions(+), 86 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 51cfb2568b..3b087245e9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -56,5 +56,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default this.ScaleTo(1, duration, Easing.OutQuint) .FadeOut(duration / 2, Easing.OutQuint); } + + protected override void OnSliderTick() + { + // TODO: Follow circle should bounce on each slider tick. + + // TEMP DUMMY ANIMS + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.1f) + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 175f); + } + + protected override void OnSliderBreak() + { + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 06fa381cbb..9eb8e66c83 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -58,21 +58,45 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - if (drawableObject is not DrawableSliderTail) - return; - Debug.Assert(ParentObject != null); - // Use ParentObject instead of drawableObject because slider tail hit state update time - // is ~36ms before the actual slider end (aka slider tail leniency) - using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime)) + switch (state) { - switch (state) - { - case ArmedState.Hit: - OnSliderEnd(); - break; - } + case ArmedState.Hit: + switch (drawableObject) + { + case DrawableSliderTail: + // Use ParentObject instead of drawableObject because slider tail's + // HitStateUpdateTime is ~36ms before the actual slider end (aka slider + // tail leniency) + using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime)) + OnSliderEnd(); + break; + + case DrawableSliderTick: + case DrawableSliderRepeat: + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderTick(); + break; + } + + break; + + case ArmedState.Miss: + switch (drawableObject) + { + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + // Despite above comment, ok to use drawableObject.HitStateUpdateTime + // here, since on stable, the break anim plays right when the tail is + // missed, not when the slider ends + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderBreak(); + break; + } + + break; } } @@ -92,5 +116,9 @@ namespace osu.Game.Rulesets.Osu.Skinning protected abstract void OnSliderRelease(); protected abstract void OnSliderEnd(); + + protected abstract void OnSliderTick(); + + protected abstract void OnSliderBreak(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 9e1c2e7e9d..6d16596ed2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyFollowCircle : TickFollowCircle + public class LegacyFollowCircle : FollowCircle { public LegacyFollowCircle(Drawable animationContent) { @@ -32,6 +32,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); } + protected override void OnSliderRelease() + { + } + protected override void OnSliderEnd() { this.ScaleTo(1.6f, 200, Easing.Out) diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs deleted file mode 100644 index ca1959a561..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ /dev/null @@ -1,73 +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 osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Skinning -{ - public abstract class TickFollowCircle : FollowCircle - { - protected override void LoadComplete() - { - base.LoadComplete(); - - if (ParentObject != null) - ParentObject.ApplyCustomUpdateState += updateStateTransforms; - } - - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) - { - // Fine to use drawableObject.HitStateUpdateTime even for DrawableSliderTail, since on - // stable, the break anim plays right when the tail is missed, not when the slider ends - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - { - switch (state) - { - case ArmedState.Hit: - switch (drawableObject) - { - case DrawableSliderTick: - case DrawableSliderRepeat: - OnSliderTick(); - break; - } - - break; - - case ArmedState.Miss: - switch (drawableObject) - { - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - OnSliderBreak(); - break; - } - - break; - } - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (ParentObject != null) - ParentObject.ApplyCustomUpdateState -= updateStateTransforms; - } - - /// - /// Sealed empty intentionally. Override instead, since - /// animations should only play on slider ticks. - /// - protected sealed override void OnSliderRelease() - { - } - - protected abstract void OnSliderTick(); - - protected abstract void OnSliderBreak(); - } -} From 16e655766eeb253f4a127bdc34d2dc3247092ba2 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 23 Jul 2022 23:30:57 +0200 Subject: [PATCH 12/34] Addressed pertinent issues --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 185751cf3d..52ed843c4c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -26,13 +26,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; - [SettingSource("Strength")] + private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles + + [SettingSource("Strength", "Multiplier applied to the wiggling strength.")] public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { MinValue = 0.1f, MaxValue = 2f, Precision = 0.1f }; + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -50,18 +53,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / 70; + int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), 70); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * 70)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration)) wiggle(); } @@ -69,11 +72,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / 70); + amountWiggles = (int)(endTime.Duration / wiggle_duration); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * 70)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration)) wiggle(); } } From f68c4e889017b5dc9e4fa01fdd253bf13cf4681f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 06:36:21 +0300 Subject: [PATCH 13/34] Fix code formatting --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 52ed843c4c..e22ba5c1db 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; - private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles + private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles [SettingSource("Strength", "Multiplier applied to the wiggling strength.")] public BindableDouble WiggleStrength { get; } = new BindableDouble(1) From b95aff3e5f4bae1917cc8c546dab295bc9874201 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 18:50:41 +0300 Subject: [PATCH 14/34] Add failing test case --- .../SongSelect/TestScenePlaySongSelect.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 12f15b04dc..3d8f496c9a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; @@ -282,6 +283,28 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("filter count is 2", () => songSelect.FilterCount == 2); } + [Test] + public void TestCarouselSelectionUpdatesOnResume() + { + addRulesetImportStep(0); + + createSongSelect(); + + AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); + AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + + AddStep("update beatmap", () => + { + var selectedBeatmap = Beatmap.Value.BeatmapInfo; + var anotherBeatmap = Beatmap.Value.BeatmapSetInfo.Beatmaps.Except(selectedBeatmap.Yield()).First(); + Beatmap.Value = manager.GetWorkingBeatmap(anotherBeatmap); + }); + + AddStep("return", () => songSelect.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); + AddAssert("carousel updated", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(Beatmap.Value.BeatmapInfo)); + } + [Test] public void TestAudioResuming() { From faefda9143ec1185cecc5ea445d6825cd67d3830 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 18:51:19 +0300 Subject: [PATCH 15/34] Fix song select not updating selected beatmap card on editor resume --- osu.Game/Screens/Select/SongSelect.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7abcbfca42..596a8eb896 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -405,20 +405,21 @@ namespace osu.Game.Screens.Select private ScheduledDelegate selectionChangedDebounce; - private void workingBeatmapChanged(ValueChangedEvent e) + private void updateCarouselSelection(ValueChangedEvent e = null) { - if (e.NewValue is DummyWorkingBeatmap || !this.IsCurrentScreen()) return; + var beatmap = e?.NewValue ?? Beatmap.Value; + if (beatmap is DummyWorkingBeatmap || !this.IsCurrentScreen()) return; - Logger.Log($"Song select working beatmap updated to {e.NewValue}"); + Logger.Log($"Song select working beatmap updated to {beatmap}"); - if (!Carousel.SelectBeatmap(e.NewValue.BeatmapInfo, false)) + if (!Carousel.SelectBeatmap(beatmap.BeatmapInfo, false)) { // A selection may not have been possible with filters applied. // There was possibly a ruleset mismatch. This is a case we can help things along by updating the game-wide ruleset to match. - if (!e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) + if (!beatmap.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) { - Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; + Ruleset.Value = beatmap.BeatmapInfo.Ruleset; transferRulesetValue(); } @@ -426,10 +427,10 @@ namespace osu.Game.Screens.Select // we still want to temporarily show the new beatmap, bypassing filters. // This will be undone the next time the user changes the filter. var criteria = FilterControl.CreateCriteria(); - criteria.SelectedBeatmapSet = e.NewValue.BeatmapInfo.BeatmapSet; + criteria.SelectedBeatmapSet = beatmap.BeatmapInfo.BeatmapSet; Carousel.Filter(criteria); - Carousel.SelectBeatmap(e.NewValue.BeatmapInfo); + Carousel.SelectBeatmap(beatmap.BeatmapInfo); } } @@ -597,6 +598,8 @@ namespace osu.Game.Screens.Select if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { + updateCarouselSelection(); + updateComponentFromBeatmap(Beatmap.Value); if (ControlGlobalMusic) @@ -805,7 +808,7 @@ namespace osu.Game.Screens.Select }; decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; - Beatmap.BindValueChanged(workingBeatmapChanged); + Beatmap.BindValueChanged(updateCarouselSelection); boundLocalBindables = true; } From 6c964dee308a48b57699fa5877e846e2cfda3ce9 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 31 Jul 2022 22:00:14 +0800 Subject: [PATCH 16/34] Rename the nullable disable annotation in the Audio namespace and mark some properties as nullable. --- osu.Game/Audio/Effects/AudioFilter.cs | 2 -- osu.Game/Audio/Effects/ITransformableFilter.cs | 2 -- osu.Game/Audio/IPreviewTrackOwner.cs | 2 -- osu.Game/Audio/ISampleInfo.cs | 2 -- osu.Game/Audio/ISamplePlaybackDisabler.cs | 2 -- osu.Game/Audio/PreviewTrack.cs | 6 ++---- osu.Game/Audio/PreviewTrackManager.cs | 8 +++----- osu.Game/Audio/SampleInfo.cs | 4 +--- 8 files changed, 6 insertions(+), 22 deletions(-) diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs index 5c318eb957..9446967173 100644 --- a/osu.Game/Audio/Effects/AudioFilter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.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.Diagnostics; using ManagedBass.Fx; using osu.Framework.Audio.Mixing; diff --git a/osu.Game/Audio/Effects/ITransformableFilter.cs b/osu.Game/Audio/Effects/ITransformableFilter.cs index 02149b362c..fb6a924f68 100644 --- a/osu.Game/Audio/Effects/ITransformableFilter.cs +++ b/osu.Game/Audio/Effects/ITransformableFilter.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.Graphics; using osu.Framework.Graphics.Transforms; diff --git a/osu.Game/Audio/IPreviewTrackOwner.cs b/osu.Game/Audio/IPreviewTrackOwner.cs index 6a3acc2059..8ab93257a5 100644 --- a/osu.Game/Audio/IPreviewTrackOwner.cs +++ b/osu.Game/Audio/IPreviewTrackOwner.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 - namespace osu.Game.Audio { /// diff --git a/osu.Game/Audio/ISampleInfo.cs b/osu.Game/Audio/ISampleInfo.cs index 8f58415587..4f81d37e78 100644 --- a/osu.Game/Audio/ISampleInfo.cs +++ b/osu.Game/Audio/ISampleInfo.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.Collections.Generic; namespace osu.Game.Audio diff --git a/osu.Game/Audio/ISamplePlaybackDisabler.cs b/osu.Game/Audio/ISamplePlaybackDisabler.cs index 250e004b05..4167316780 100644 --- a/osu.Game/Audio/ISamplePlaybackDisabler.cs +++ b/osu.Game/Audio/ISamplePlaybackDisabler.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.Game.Skinning; diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 8ff8cd5c54..cfedd581e5 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.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; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -18,13 +16,13 @@ namespace osu.Game.Audio /// Invoked when this has stopped playing. /// Not invoked in a thread-safe context. /// - public event Action Stopped; + public event Action? Stopped; /// /// Invoked when this has started playing. /// Not invoked in a thread-safe context. /// - public event Action Started; + public event Action? Started; protected Track Track { get; private set; } diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index d19fdbd94c..a0537d7a4e 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.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.Audio; using osu.Framework.Audio.Track; @@ -20,9 +18,9 @@ namespace osu.Game.Audio private readonly BindableDouble muteBindable = new BindableDouble(); - private ITrackStore trackStore; + private ITrackStore trackStore = null!; - protected TrackManagerPreviewTrack CurrentTrack; + protected TrackManagerPreviewTrack? CurrentTrack; public PreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments) { @@ -90,7 +88,7 @@ namespace osu.Game.Audio public class TrackManagerPreviewTrack : PreviewTrack { [Resolved(canBeNull: true)] - public IPreviewTrackOwner Owner { get; private set; } + public IPreviewTrackOwner? Owner { get; private set; } private readonly IBeatmapSetInfo beatmapSetInfo; private readonly ITrackStore trackManager; diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 54b380f23a..19c78f34b2 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.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; using System.Collections; using System.Collections.Generic; @@ -34,7 +32,7 @@ namespace osu.Game.Audio Volume); } - public bool Equals(SampleInfo other) + public bool Equals(SampleInfo? other) => other != null && sampleNames.SequenceEqual(other.sampleNames); public override bool Equals(object obj) From 094793bbe3fdfcdc5769e821a5d19bf3c0d5e843 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 31 Jul 2022 22:01:30 +0800 Subject: [PATCH 17/34] Mark the GetTrack() return type as nullable. --- osu.Game/Audio/PreviewTrack.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index cfedd581e5..7fb92f9f9d 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -24,7 +24,7 @@ namespace osu.Game.Audio /// public event Action? Started; - protected Track Track { get; private set; } + protected Track? Track { get; private set; } private bool hasStarted; @@ -56,7 +56,7 @@ namespace osu.Game.Audio /// public bool IsRunning => Track?.IsRunning ?? false; - private ScheduledDelegate startDelegate; + private ScheduledDelegate? startDelegate; /// /// Starts playing this . @@ -104,6 +104,6 @@ namespace osu.Game.Audio /// /// Retrieves the audio track. /// - protected abstract Track GetTrack(); + protected abstract Track? GetTrack(); } } From 5dd641bc60065e5555a8c50b04387cdd0febe33b Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 31 Jul 2022 22:02:07 +0800 Subject: [PATCH 18/34] Remove the nullable disable annotation in the test project. --- osu.Game.Tests/Audio/SampleInfoEqualityTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs b/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs index d05eb7994b..149096608f 100644 --- a/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs +++ b/osu.Game.Tests/Audio/SampleInfoEqualityTest.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 NUnit.Framework; using osu.Game.Audio; From e0940c6c22e28f24d120d764e1c63eb024016f56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 18:03:32 +0900 Subject: [PATCH 19/34] Update animations to final versions --- .../Skinning/Default/DefaultFollowCircle.cs | 8 +++----- .../Skinning/Legacy/LegacyFollowCircle.cs | 5 +---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 3b087245e9..aaace89cd5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -59,11 +59,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnSliderTick() { - // TODO: Follow circle should bounce on each slider tick. - - // TEMP DUMMY ANIMS - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.1f) - .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 175f); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); } protected override void OnSliderBreak() diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 6d16596ed2..0d12fb01f5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -44,11 +44,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void OnSliderTick() { - // TODO: Follow circle should bounce on each slider tick. - - // TEMP DUMMY ANIMS this.ScaleTo(2.2f) - .ScaleTo(2f, 175f); + .ScaleTo(2f, 200); } protected override void OnSliderBreak() From 6cccb6b84825dd4f58ddb5d5e8ea679f0a57f4bb Mon Sep 17 00:00:00 2001 From: andy840119 Date: Mon, 1 Aug 2022 19:45:15 +0800 Subject: [PATCH 20/34] Remove `canBeNull: true`. --- osu.Game/Audio/PreviewTrackManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index a0537d7a4e..b8662b6a4b 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -87,7 +87,7 @@ namespace osu.Game.Audio public class TrackManagerPreviewTrack : PreviewTrack { - [Resolved(canBeNull: true)] + [Resolved] public IPreviewTrackOwner? Owner { get; private set; } private readonly IBeatmapSetInfo beatmapSetInfo; From a5fac70c3b468b9788d5e783f65fb94ca80fce4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 23:30:00 +0900 Subject: [PATCH 21/34] Rename variable to not include mode name itself --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index f3d944d55c..3f1c3aa812 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles [SettingSource("Strength", "Multiplier applied to the wiggling strength.")] - public BindableDouble WiggleStrength { get; } = new BindableDouble(1) + public BindableDouble Strength { get; } = new BindableDouble(1) { MinValue = 0.1f, MaxValue = 2f, @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); + float nextDist = (float)(objRand.NextDouble() * Strength.Value * 7); drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); } From 682192dbd7fc53d8e642d84a3f2c9cf780ec83e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 18:43:01 +0300 Subject: [PATCH 22/34] Add failing test case --- .../SongSelect/TestSceneBeatmapCarousel.cs | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 59932f8781..bb9e83a21c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -486,9 +486,6 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null); } - /// - /// Test sorting - /// [Test] public void TestSorting() { @@ -517,6 +514,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_string); } + /// + /// Ensures stability is maintained on different sort modes for items with equal properties. + /// [Test] public void TestSortingStability() { @@ -549,6 +549,53 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Items reset to original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); } + /// + /// Ensures stability is maintained on different sort modes while a new item is added to the carousel. + /// + [Test] + public void TestSortingStabilityWithNewItems() + { + List sets = new List(); + + for (int i = 0; i < 3; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(3); + + // only need to set the first as they are a shared reference. + var beatmap = set.Beatmaps.First(); + + beatmap.Metadata.Artist = "same artist"; + beatmap.Metadata.Title = "same title"; + + sets.Add(set); + } + + int idOffset = sets.First().OnlineID; + + loadBeatmaps(sets); + + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + + AddStep("Add new item", () => + { + var set = TestResources.CreateTestBeatmapSetInfo(); + + // only need to set the first as they are a shared reference. + var beatmap = set.Beatmaps.First(); + + beatmap.Metadata.Artist = "same artist"; + beatmap.Metadata.Title = "same title"; + + carousel.UpdateBeatmapSet(set); + }); + + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + + AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + } + [Test] public void TestSortingWithFiltered() { From fc7fc3d673cf0a843d4eeea0225bcde76b987c36 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 19:13:57 +0300 Subject: [PATCH 23/34] Fix newly imported beatmaps not using correct comparer for sorting --- .../Screens/Select/Carousel/CarouselGroup.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 8d141b6f72..9302578038 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; namespace osu.Game.Screens.Select.Carousel { @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Select.Carousel public IReadOnlyList Items => items; - private List items = new List(); + private readonly List items = new List(); /// /// Used to assign a monotonically increasing ID to items as they are added. This member is @@ -24,9 +23,6 @@ namespace osu.Game.Screens.Select.Carousel private ulong currentItemID; private Comparer? criteriaComparer; - - private static readonly Comparer item_id_comparer = Comparer.Create((x, y) => x.ItemID.CompareTo(y.ItemID)); - private FilterCriteria? lastCriteria; protected int GetIndexOfItem(CarouselItem lastSelected) => items.IndexOf(lastSelected); @@ -90,9 +86,16 @@ namespace osu.Game.Screens.Select.Carousel items.ForEach(c => c.Filter(criteria)); - // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability - criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); - items = items.OrderBy(c => c, criteriaComparer).ThenBy(c => c, item_id_comparer).ToList(); + criteriaComparer = Comparer.Create((x, y) => + { + int comparison = x.CompareTo(criteria, y); + if (comparison != 0) + return comparison; + + return x.ItemID.CompareTo(y.ItemID); + }); + + items.Sort(criteriaComparer); lastCriteria = criteria; } From 1fe7e4d19a51c8cd8a15122ccb8d5b20c0bfb4bd Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 2 Aug 2022 00:45:47 +0800 Subject: [PATCH 24/34] Use non-nullable instead in the catch ruleset. --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 5 ++--- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4824106c55..abe391ba4e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; @@ -36,9 +35,9 @@ namespace osu.Game.Rulesets.Catch.Mods public override float DefaultFlashlightSize => 350; - protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield.AsNonNull()); + protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); - private CatchPlayfield? playfield; + private CatchPlayfield playfield = null!; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 0ab6da0363..60f1614d98 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,7 +1,6 @@ // 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.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override string Description => @"Use the mouse to control the catcher."; - private DrawableRuleset? drawableRuleset; + private DrawableRuleset drawableRuleset = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -28,8 +27,6 @@ namespace osu.Game.Rulesets.Catch.Mods public void ApplyToPlayer(Player player) { - Debug.Assert(drawableRuleset != null); - if (!drawableRuleset.HasReplayLoaded.Value) drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield)); } From b1d320bf6700285957102206ade738b3a6b1d318 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 2 Aug 2022 00:48:23 +0800 Subject: [PATCH 25/34] Use non-nullable instead in the taiko ruleset. --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 5 ++--- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 8872de4d7a..66616486df 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Layout; using osu.Game.Configuration; @@ -37,9 +36,9 @@ namespace osu.Game.Rulesets.Taiko.Mods public override float DefaultFlashlightSize => 250; - protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield.AsNonNull()); + protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); - private TaikoPlayfield? playfield; + private TaikoPlayfield playfield = null!; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index dab2279351..ec39a2b2e5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private const float fade_out_duration = 0.375f; - private DrawableTaikoRuleset? drawableRuleset; + private DrawableTaikoRuleset drawableRuleset = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - Debug.Assert(drawableRuleset != null); - switch (hitObject) { case DrawableDrumRollTick: From 6686b095492abdd1eb0909eea6b57a6c33513b93 Mon Sep 17 00:00:00 2001 From: notmyname <50967056+naipofo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:54:00 +0200 Subject: [PATCH 26/34] Hide F rank from beatmap overlay --- osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index 118ddfb060..09b44be6c9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing { protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); - protected override IEnumerable GetValues() => base.GetValues().Reverse(); + protected override IEnumerable GetValues() => base.GetValues().Where(r => r > ScoreRank.F).Reverse(); } private class RankItem : MultipleSelectionFilterTabItem From c851e3d8f3150dc965be7e0c5d20a25a1da44d8e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 20:08:18 +0300 Subject: [PATCH 27/34] Fix playlist settings reference leak due to unsafe callback binding --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 9c6a2a5e0b..a59d37dc8b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IAPIProvider api { get; set; } + private IBindable localUser; + private readonly Room room; public MatchSettings(Room room) @@ -304,7 +306,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); - api.LocalUser.BindValueChanged(populateDurations, true); + localUser = api.LocalUser.GetBoundCopy(); + localUser.BindValueChanged(populateDurations, true); playlist.Items.BindTo(Playlist); Playlist.BindCollectionChanged(onPlaylistChanged, true); From 7e9d11ee2486e04b06f92d3edeb19088ffcf3e68 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 20:15:08 +0300 Subject: [PATCH 28/34] Enable NRT on playlists settings overlay --- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index a59d37dc8b..cd52981528 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.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; using System.Collections.Specialized; using System.Linq; @@ -29,9 +27,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsRoomSettingsOverlay : RoomSettingsOverlay { - public Action EditPlaylist; + public Action? EditPlaylist; - private MatchSettings settings; + private MatchSettings settings = null!; protected override OsuButton SubmitButton => settings.ApplyButton; @@ -55,30 +53,30 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { private const float disabled_alpha = 0.2f; - public Action EditPlaylist; + public Action? EditPlaylist; - public OsuTextBox NameField, MaxParticipantsField, MaxAttemptsField; - public OsuDropdown DurationField; - public RoomAvailabilityPicker AvailabilityPicker; - public TriangleButton ApplyButton; + public OsuTextBox NameField = null!, MaxParticipantsField = null!, MaxAttemptsField = null!; + public OsuDropdown DurationField = null!; + public RoomAvailabilityPicker AvailabilityPicker = null!; + public TriangleButton ApplyButton = null!; public bool IsLoading => loadingLayer.State.Value == Visibility.Visible; - public OsuSpriteText ErrorText; + public OsuSpriteText ErrorText = null!; - private LoadingLayer loadingLayer; - private DrawableRoomPlaylist playlist; - private OsuSpriteText playlistLength; + private LoadingLayer loadingLayer = null!; + private DrawableRoomPlaylist playlist = null!; + private OsuSpriteText playlistLength = null!; - private PurpleTriangleButton editPlaylistButton; - - [Resolved(CanBeNull = true)] - private IRoomManager manager { get; set; } + private PurpleTriangleButton editPlaylistButton = null!; [Resolved] - private IAPIProvider api { get; set; } + private IRoomManager? manager { get; set; } - private IBindable localUser; + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private IBindable localUser = null!; private readonly Room room; From 923d9a4e5f19aa10273f99a058b51fcecf5b4757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 Aug 2022 22:04:14 +0200 Subject: [PATCH 29/34] Add failing assertions to demonstrate autosize failure --- .../UserInterface/TestSceneShearedButtons.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs index ba9e1c6366..6c485aff34 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs @@ -3,9 +3,13 @@ #nullable disable +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK.Input; @@ -99,7 +103,10 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Text = "Fixed width" }); + AddAssert("draw width is 200", () => toggleButton.DrawWidth, () => Is.EqualTo(200).Within(Precision.FLOAT_EPSILON)); + AddStep("change text", () => toggleButton.Text = "New text"); + AddAssert("draw width is 200", () => toggleButton.DrawWidth, () => Is.EqualTo(200).Within(Precision.FLOAT_EPSILON)); AddStep("create auto-sizing button", () => Child = toggleButton = new ShearedToggleButton { @@ -107,7 +114,14 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Text = "This button autosizes to its text!" }); + AddAssert("button is wider than text", () => toggleButton.DrawWidth, () => Is.GreaterThan(toggleButton.ChildrenOfType().Single().DrawWidth)); + + float originalDrawWidth = 0; + AddStep("store button width", () => originalDrawWidth = toggleButton.DrawWidth); + AddStep("change text", () => toggleButton.Text = "New text"); + AddAssert("button is wider than text", () => toggleButton.DrawWidth, () => Is.GreaterThan(toggleButton.ChildrenOfType().Single().DrawWidth)); + AddAssert("button width decreased", () => toggleButton.DrawWidth, () => Is.LessThan(originalDrawWidth)); } [Test] From 298efa5391734206c8c8d896beb02a940b2b28d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 21:40:24 +0200 Subject: [PATCH 30/34] Fix broken `ShearedButton` autosizing logic --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index 259c0646f3..0c25d06cd4 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -97,7 +97,7 @@ namespace osu.Game.Graphics.UserInterface { backgroundLayer = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Y, CornerRadius = corner_radius, Masking = true, BorderThickness = 2, @@ -128,10 +128,12 @@ namespace osu.Game.Graphics.UserInterface if (width != null) { Width = width.Value; + backgroundLayer.RelativeSizeAxes = Axes.Both; } else { AutoSizeAxes = Axes.X; + backgroundLayer.AutoSizeAxes = Axes.X; text.Margin = new MarginPadding { Horizontal = 15 }; } } From eb73f9e88c2616bc42ff81a2b5f0e7729a798e9f Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 2 Aug 2022 10:23:52 +0800 Subject: [PATCH 31/34] Remove un-need using. --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index ec39a2b2e5..4c802978e3 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,7 +1,6 @@ // 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.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; From 34ffc51c5195196873aefda972549e20b5e1a1f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Aug 2022 13:56:02 +0900 Subject: [PATCH 32/34] Avoid clearing chat overlay textbox when pressing "back" key binding Generally this is expected behaviour for usages of focused text boxes (ie. to clear search content), but not so much here. Addresses https://github.com/ppy/osu/discussions/19403#discussioncomment-3230395. --- osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 7 ++++++- osu.Game/Overlays/Chat/ChatTextBox.cs | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index fce4221c87..230d921c68 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -21,6 +21,11 @@ namespace osu.Game.Graphics.UserInterface private bool allowImmediateFocus => host?.OnScreenKeyboardOverlapsGameWindow != true; + /// + /// Whether the content of the text box should be cleared on the first "back" key press. + /// + protected virtual bool ClearTextOnBackKey => true; + public void TakeFocus() { if (!allowImmediateFocus) @@ -78,7 +83,7 @@ namespace osu.Game.Graphics.UserInterface if (!HasFocus) return false; - if (e.Action == GlobalAction.Back) + if (ClearTextOnBackKey && e.Action == GlobalAction.Back) { if (Text.Length > 0) { diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index ae8816b009..887eb96c15 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -13,6 +13,8 @@ namespace osu.Game.Overlays.Chat public override bool HandleLeftRightArrows => !ShowSearch.Value; + protected override bool ClearTextOnBackKey => false; + protected override void LoadComplete() { base.LoadComplete(); From f743dc623f6337fd80259de8768776e9ba59db3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Aug 2022 17:37:30 +0900 Subject: [PATCH 33/34] Change migration logic to ignore realm pipe files regardless of database filename --- osu.Game/IO/MigratableStorage.cs | 17 +++++++++++++++++ osu.Game/IO/OsuStorage.cs | 11 ++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 7cd409c04c..14a3c5a43c 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -26,6 +26,11 @@ namespace osu.Game.IO /// public virtual string[] IgnoreFiles => Array.Empty(); + /// + /// A list of file/directory suffixes which should not be migrated. + /// + public virtual string[] IgnoreSuffixes => Array.Empty(); + protected MigratableStorage(Storage storage, string subPath = null) : base(storage, subPath) { @@ -73,6 +78,9 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; + if (IgnoreSuffixes.Any(suffix => fi.Name.EndsWith(suffix, StringComparison.Ordinal))) + continue; + allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false); } @@ -81,6 +89,9 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; + if (IgnoreSuffixes.Any(suffix => dir.Name.EndsWith(suffix, StringComparison.Ordinal))) + continue; + allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false); } @@ -101,6 +112,9 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreFiles.Contains(fileInfo.Name)) continue; + if (IgnoreSuffixes.Any(suffix => fileInfo.Name.EndsWith(suffix, StringComparison.Ordinal))) + continue; + AttemptOperation(() => { fileInfo.Refresh(); @@ -119,6 +133,9 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; + if (IgnoreSuffixes.Any(suffix => dir.Name.EndsWith(suffix, StringComparison.Ordinal))) + continue; + CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); } } diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 368ac56850..f4c55e4b0e 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -38,15 +38,20 @@ namespace osu.Game.IO public override string[] IgnoreDirectories => new[] { "cache", - $"{OsuGameBase.CLIENT_DATABASE_FILENAME}.management", }; public override string[] IgnoreFiles => new[] { "framework.ini", "storage.ini", - $"{OsuGameBase.CLIENT_DATABASE_FILENAME}.note", - $"{OsuGameBase.CLIENT_DATABASE_FILENAME}.lock", + }; + + public override string[] IgnoreSuffixes => new[] + { + // Realm pipe files don't play well with copy operations + ".note", + ".lock", + ".management", }; public OsuStorage(GameHost host, Storage defaultStorage) From 6ad6561e1cd8c73f9ad7237ce1c51ed389e24656 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Aug 2022 17:42:27 +0900 Subject: [PATCH 34/34] Fix `LegacySongProgress` incorrectly blocking mouse input from gameplay Closes #19555. --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 2 -- osu.Game/Screens/Play/HUD/SongProgress.cs | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 96a6c56860..659984682e 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -40,8 +40,6 @@ namespace osu.Game.Screens.Play.HUD public override bool HandleNonPositionalInput => AllowSeeking.Value; public override bool HandlePositionalInput => AllowSeeking.Value; - protected override bool BlockScrollInput => false; - [Resolved] private Player? player { get; set; } diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 702d2f7c6f..09afd7a9d3 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -14,6 +14,12 @@ namespace osu.Game.Screens.Play.HUD { public abstract class SongProgress : OverlayContainer, ISkinnableDrawable { + // Some implementations of this element allow seeking during gameplay playback. + // Set a sane default of never handling input to override the behaviour provided by OverlayContainer. + public override bool HandleNonPositionalInput => false; + public override bool HandlePositionalInput => false; + protected override bool BlockScrollInput => false; + public bool UsesFixedAnchor { get; set; } [Resolved]