From ca061c4b939fdc8b8782ba0e80885f17da3965d5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:41:51 +0900 Subject: [PATCH 01/24] Factor out `SkinnableDrawable` component of the catcher to `SkinnableCatcher` --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 37 ++++++------------- .../UI/SkinnableCatcher.cs | 26 +++++++++++++ 2 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index ee2986c73c..dce89a9dae 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Skinning; -using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Rulesets.Judgements; using osu.Game.Skinning; using osuTK; @@ -83,17 +82,18 @@ namespace osu.Game.Rulesets.Catch.UI /// private readonly Container droppedObjectTarget; - [Cached] - protected readonly Bindable CurrentStateBindable = new Bindable(); - - public CatcherAnimationState CurrentState => CurrentStateBindable.Value; + public CatcherAnimationState CurrentState + { + get => body.AnimationState.Value; + private set => body.AnimationState.Value = value; + } /// /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. /// public const float ALLOWED_CATCH_RANGE = 0.8f; - internal Texture CurrentTexture => ((ICatcherSprite)currentCatcher.Drawable).CurrentTexture; + internal Texture CurrentTexture => ((ICatcherSprite)body.Drawable).CurrentTexture; private bool dashing; @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private readonly float catchWidth; - private readonly SkinnableDrawable currentCatcher; + private readonly SkinnableCatcher body; private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; @@ -161,13 +161,7 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, }, - currentCatcher = new SkinnableDrawable( - new CatchSkinComponent(CatchSkinComponents.Catcher), - _ => new DefaultCatcher()) - { - Anchor = Anchor.TopCentre, - OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE - }, + body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer { Anchor = Anchor.TopCentre, @@ -268,17 +262,16 @@ namespace osu.Game.Rulesets.Catch.UI SetHyperDashState(); if (result.IsHit) - updateState(hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle); + CurrentState = hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle; else if (!(hitObject is Banana)) - updateState(CatcherAnimationState.Fail); + CurrentState = CatcherAnimationState.Fail; } public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result) { var catchResult = (CatchJudgementResult)result; - if (CurrentState != catchResult.CatcherAnimationState) - updateState(catchResult.CatcherAnimationState); + CurrentState = catchResult.CatcherAnimationState; if (HyperDashing != catchResult.CatcherHyperDash) { @@ -373,14 +366,6 @@ namespace osu.Game.Rulesets.Catch.UI } } - private void updateState(CatcherAnimationState state) - { - if (CurrentState == state) - return; - - CurrentStateBindable.Value = state; - } - private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position) { var caughtObject = getCaughtObject(drawableObject.HitObject); diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs new file mode 100644 index 0000000000..5bd97858b2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs @@ -0,0 +1,26 @@ +// 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.Game.Rulesets.Catch.Skinning.Default; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class SkinnableCatcher : SkinnableDrawable + { + [Cached] + public readonly Bindable AnimationState = new Bindable(); + + public SkinnableCatcher() + : base(new CatchSkinComponent(CatchSkinComponents.Catcher), _ => new DefaultCatcher()) + { + Anchor = Anchor.TopCentre; + // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling. + OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE; + } + } +} From 9b6ab4360e497a7b0e32fa1d21bf490d8d70e943 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:45:58 +0900 Subject: [PATCH 02/24] Use common skinnable catcher in catcher trails --- .../UI/CatcherTrailDisplay.cs | 4 ++-- .../UI/CatcherTrailSprite.cs | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 0aef215797..dbe41121c8 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -120,8 +120,8 @@ namespace osu.Game.Rulesets.Catch.UI { CatcherTrailSprite sprite = trailPool.Get(); - sprite.Texture = catcher.CurrentTexture; - sprite.Anchor = catcher.Anchor; + sprite.AnimationState = catcher.CurrentState; + sprite.Origin = catcher.Origin; sprite.Scale = catcher.Scale; sprite.Blending = BlendingParameters.Additive; sprite.RelativePositionAxes = catcher.RelativePositionAxes; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs index 0e3e409fac..c4bb0aa1f2 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs @@ -3,32 +3,24 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osuTK; namespace osu.Game.Rulesets.Catch.UI { public class CatcherTrailSprite : PoolableDrawable { - public Texture Texture + public CatcherAnimationState AnimationState { - set => sprite.Texture = value; + set => body.AnimationState.Value = value; } - private readonly Sprite sprite; + private readonly SkinnableCatcher body; public CatcherTrailSprite() { - InternalChild = sprite = new Sprite - { - RelativeSizeAxes = Axes.Both - }; - Size = new Vector2(CatcherArea.CATCHER_SIZE); - - // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling. - OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE; + Origin = Anchor.TopCentre; + InternalChild = body = new SkinnableCatcher(); } protected override void FreeAfterUse() From c094914023aab5e29e71cba493a5858233fbd8cc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:46:48 +0900 Subject: [PATCH 03/24] Simplify catcher trail creation --- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 3 --- osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index dbe41121c8..382e796480 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -121,10 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI CatcherTrailSprite sprite = trailPool.Get(); sprite.AnimationState = catcher.CurrentState; - sprite.Origin = catcher.Origin; sprite.Scale = catcher.Scale; - sprite.Blending = BlendingParameters.Additive; - sprite.RelativePositionAxes = catcher.RelativePositionAxes; sprite.Position = catcher.Position; target.Add(sprite); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs index c4bb0aa1f2..8417e5a250 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.UI { Size = new Vector2(CatcherArea.CATCHER_SIZE); Origin = Anchor.TopCentre; + Blending = BlendingParameters.Additive; InternalChild = body = new SkinnableCatcher(); } From 38a56d64d316404ac0ca2260ca1ad2efc46a5b94 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 19:47:18 +0900 Subject: [PATCH 04/24] Rename `CatcherTrailSprite` -> `CatcherTrail` --- ...{CatcherTrailSprite.cs => CatcherTrail.cs} | 4 ++-- .../UI/CatcherTrailDisplay.cs | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game.Rulesets.Catch/UI/{CatcherTrailSprite.cs => CatcherTrail.cs} (90%) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs similarity index 90% rename from osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs rename to osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 8417e5a250..90fb59db9a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.UI { - public class CatcherTrailSprite : PoolableDrawable + public class CatcherTrail : PoolableDrawable { public CatcherAnimationState AnimationState { @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly SkinnableCatcher body; - public CatcherTrailSprite() + public CatcherTrail() { Size = new Vector2(CatcherArea.CATCHER_SIZE); Origin = Anchor.TopCentre; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 382e796480..7e4a5b6a86 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Rulesets.Catch.UI { private readonly Catcher catcher; - private readonly DrawablePool trailPool; + private readonly DrawablePool trailPool; - private readonly Container dashTrails; - private readonly Container hyperDashTrails; - private readonly Container endGlowSprites; + private readonly Container dashTrails; + private readonly Container hyperDashTrails; + private readonly Container endGlowSprites; private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; @@ -83,10 +83,10 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new Drawable[] { - trailPool = new DrawablePool(30), - dashTrails = new Container { RelativeSizeAxes = Axes.Both }, - hyperDashTrails = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, - endGlowSprites = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, + trailPool = new DrawablePool(30), + dashTrails = new Container { RelativeSizeAxes = Axes.Both }, + hyperDashTrails = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, + endGlowSprites = new Container { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR }, }; } @@ -116,9 +116,9 @@ namespace osu.Game.Rulesets.Catch.UI Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50); } - private CatcherTrailSprite createTrailSprite(Container target) + private CatcherTrail createTrailSprite(Container target) { - CatcherTrailSprite sprite = trailPool.Get(); + CatcherTrail sprite = trailPool.Get(); sprite.AnimationState = catcher.CurrentState; sprite.Scale = catcher.Scale; From df16d4baccff180aadb9a441d9315f11f1e67811 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 14 Jun 2021 20:26:33 +0900 Subject: [PATCH 05/24] Remove `CurrentTexture` from catcher --- .../Skinning/Default/DefaultCatcher.cs | 4 +--- osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs | 12 ------------ .../Skinning/Legacy/LegacyCatcherNew.cs | 6 +----- .../Skinning/Legacy/LegacyCatcherOld.cs | 7 +------ osu.Game.Rulesets.Catch/UI/Catcher.cs | 3 --- 5 files changed, 3 insertions(+), 29 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs index 364fc211a0..e423f21b98 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs @@ -12,12 +12,10 @@ using osu.Game.Rulesets.Catch.UI; namespace osu.Game.Rulesets.Catch.Skinning.Default { - public class DefaultCatcher : CompositeDrawable, ICatcherSprite + public class DefaultCatcher : CompositeDrawable { public Bindable CurrentState { get; } = new Bindable(); - public Texture CurrentTexture => sprite.Texture; - private readonly Sprite sprite; private readonly Dictionary textures = new Dictionary(); diff --git a/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs b/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs deleted file mode 100644 index 073868e947..0000000000 --- a/osu.Game.Rulesets.Catch/Skinning/ICatcherSprite.cs +++ /dev/null @@ -1,12 +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.Framework.Graphics.Textures; - -namespace osu.Game.Rulesets.Catch.Skinning -{ - public interface ICatcherSprite - { - Texture CurrentTexture { get; } - } -} diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index 2bf8b28aa2..9df87c92ea 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -9,21 +9,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Rulesets.Catch.UI; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { - public class LegacyCatcherNew : CompositeDrawable, ICatcherSprite + public class LegacyCatcherNew : CompositeDrawable { [Resolved] private Bindable currentState { get; set; } - public Texture CurrentTexture => (currentDrawable as TextureAnimation)?.CurrentFrame ?? (currentDrawable as Sprite)?.Texture; - private readonly Dictionary drawables = new Dictionary(); private Drawable currentDrawable; diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs index a8948d2ed0..3e679171b2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs @@ -3,19 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { - public class LegacyCatcherOld : CompositeDrawable, ICatcherSprite + public class LegacyCatcherOld : CompositeDrawable { - public Texture CurrentTexture => (InternalChild as TextureAnimation)?.CurrentFrame ?? (InternalChild as Sprite)?.Texture; - public LegacyCatcherOld() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dce89a9dae..1f01dbabb5 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -93,8 +92,6 @@ namespace osu.Game.Rulesets.Catch.UI /// public const float ALLOWED_CATCH_RANGE = 0.8f; - internal Texture CurrentTexture => ((ICatcherSprite)body.Drawable).CurrentTexture; - private bool dashing; public bool Dashing From 73e443a0d9c5bc67192fef049d33dc8f477decb5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 14:01:12 +0900 Subject: [PATCH 06/24] Add comments --- osu.Game.Rulesets.Catch/UI/CatcherTrail.cs | 6 ++++++ osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 90fb59db9a..f273ef23ac 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -7,6 +7,12 @@ using osuTK; namespace osu.Game.Rulesets.Catch.UI { + /// + /// A trail of the catcher. + /// It also represents a hyper dash afterimage. + /// + // TODO: Trails shouldn't be animated when the skin has an animated catcher. + // The animation should be frozen at the animation frame at the time of the trail generation. public class CatcherTrail : PoolableDrawable { public CatcherAnimationState AnimationState diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs index 5bd97858b2..1038af7a48 100644 --- a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs +++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs @@ -10,6 +10,10 @@ using osuTK; namespace osu.Game.Rulesets.Catch.UI { + /// + /// The visual representation of the . + /// It includes the body part of the catcher and the catcher plate. + /// public class SkinnableCatcher : SkinnableDrawable { [Cached] From 1632450918c9af1955f32fdc1a6a676550087440 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 16:17:14 +0900 Subject: [PATCH 07/24] Add comments --- osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs index 1038af7a48..fc34ba4c8b 100644 --- a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs +++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs @@ -16,6 +16,9 @@ namespace osu.Game.Rulesets.Catch.UI /// public class SkinnableCatcher : SkinnableDrawable { + /// + /// This is used by skin elements to determine which texture of the catcher is used. + /// [Cached] public readonly Bindable AnimationState = new Bindable(); From b087c95581e960f80fa26af370aceb15dadf251e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 16 Jun 2021 16:14:42 +0900 Subject: [PATCH 08/24] Use a frozen clock for catcher trails --- osu.Game.Rulesets.Catch/UI/CatcherTrail.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index f273ef23ac..80522ab36b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; +using osu.Framework.Timing; using osuTK; namespace osu.Game.Rulesets.Catch.UI @@ -11,8 +12,6 @@ namespace osu.Game.Rulesets.Catch.UI /// A trail of the catcher. /// It also represents a hyper dash afterimage. /// - // TODO: Trails shouldn't be animated when the skin has an animated catcher. - // The animation should be frozen at the animation frame at the time of the trail generation. public class CatcherTrail : PoolableDrawable { public CatcherAnimationState AnimationState @@ -27,7 +26,12 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(CatcherArea.CATCHER_SIZE); Origin = Anchor.TopCentre; Blending = BlendingParameters.Additive; - InternalChild = body = new SkinnableCatcher(); + InternalChild = body = new SkinnableCatcher + { + // Using a frozen clock because trails should not be animated when the skin has an animated catcher. + // TODO: The animation should be frozen at the animation frame at the time of the trail generation. + Clock = new FramedClock(new ManualClock()), + }; } protected override void FreeAfterUse() From 4b926791b58344f6018941ac6463849bc742bdb5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 16 Jun 2021 21:13:01 +0700 Subject: [PATCH 09/24] add inter font --- osu.Game/OsuGameBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9c3adba342..abf8fbc4fb 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -195,6 +195,15 @@ namespace osu.Game AddFont(Resources, @"Fonts/Torus-SemiBold"); AddFont(Resources, @"Fonts/Torus-Bold"); + AddFont(Resources, @"Fonts/Inter-Regular"); + AddFont(Resources, @"Fonts/Inter-RegularItalic"); + AddFont(Resources, @"Fonts/Inter-Light"); + AddFont(Resources, @"Fonts/Inter-LightItalic"); + AddFont(Resources, @"Fonts/Inter-SemiBold"); + AddFont(Resources, @"Fonts/Inter-SemiBoldItalic"); + AddFont(Resources, @"Fonts/Inter-Bold"); + AddFont(Resources, @"Fonts/Inter-BoldItalic"); + AddFont(Resources, @"Fonts/Noto-Basic"); AddFont(Resources, @"Fonts/Noto-Hangul"); AddFont(Resources, @"Fonts/Noto-CJK-Basic"); From 5933e0d2d99a30ebc57b83a8dad81934746eabc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 13:17:32 +0900 Subject: [PATCH 10/24] Change `CheckCompatibleSet` to never deselect the current candidat when checking incompatibility --- osu.Game.Tests/Mods/ModUtilsTest.cs | 30 +++++++++++++++++++++++++++-- osu.Game/Utils/ModUtils.cs | 3 +++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 7384471c41..9f27289d7e 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -21,6 +21,14 @@ namespace osu.Game.Tests.Mods Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); } + [Test] + public void TestModIsCompatibleByItselfWithIncompatibleInterface() + { + var mod = new Mock(); + mod.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(IModCompatibilitySpecification) }); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); + } + [Test] public void TestIncompatibleThroughTopLevel() { @@ -34,6 +42,20 @@ namespace osu.Game.Tests.Mods Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False); } + [Test] + public void TestIncompatibleThroughInterface() + { + var mod1 = new Mock(); + var mod2 = new Mock(); + + mod1.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(IModCompatibilitySpecification) }); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(IModCompatibilitySpecification) }); + + // Test both orderings. + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False); + } + [Test] public void TestMultiModIncompatibleWithTopLevel() { @@ -149,11 +171,15 @@ namespace osu.Game.Tests.Mods Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } - public abstract class CustomMod1 : Mod + public abstract class CustomMod1 : Mod, IModCompatibilitySpecification { } - public abstract class CustomMod2 : Mod + public abstract class CustomMod2 : Mod, IModCompatibilitySpecification + { + } + + public interface IModCompatibilitySpecification { } } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 1c3558fc90..98766cb844 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -60,6 +60,9 @@ namespace osu.Game.Utils { foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m))) { + if (invalid == mod) + continue; + invalidMods ??= new List(); invalidMods.Add(invalid); } From 4de27429bc0dc65f988c8b1e67941464ce840bb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 13:17:55 +0900 Subject: [PATCH 11/24] Change `ModSelectOverlay` to never deselect the user triggered selection --- osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSection.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs index 78cd9bdae5..db76581108 100644 --- a/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Mods base.OnModSelected(mod); foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods, true); + section.DeselectTypes(mod.IncompatibleMods, true, mod); } } } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index aa8a5efd39..6e289dc8aa 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -159,12 +159,16 @@ namespace osu.Game.Overlays.Mods /// /// The types of s which should be deselected. /// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow. - public void DeselectTypes(IEnumerable modTypes, bool immediate = false) + /// If this deselection is triggered by a user selection, this should contain the newly selected type. This type will never be deselected, even if it matches one provided in . + public void DeselectTypes(IEnumerable modTypes, bool immediate = false, Mod newSelection = null) { foreach (var button in Buttons) { if (button.SelectedMod == null) continue; + if (button.SelectedMod == newSelection) + continue; + foreach (var type in modTypes) { if (type.IsInstanceOfType(button.SelectedMod)) From 860626152aedc57dc941031e60067ae63c253181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 12:54:40 +0900 Subject: [PATCH 12/24] Mark all mods which adjust approach circle as incompatible with each other Closes https://github.com/ppy/osu/issues/13543. --- osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs | 12 ++++++++++++ .../Mods/OsuModApproachDifferent.cs | 5 ++++- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 ++-- 6 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs diff --git a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs new file mode 100644 index 0000000000..60a5825241 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Osu.Mods +{ + /// + /// Any mod which affects the animation or visibility of approach circles. Should be used for incompatibility purposes. + /// + public interface IMutateApproachCircles + { + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 074fb7dbed..526e29ad53 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -11,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject + public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IMutateApproachCircles { public override string Name => "Approach Different"; public override string Acronym => "AD"; @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; + [SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)] public BindableFloat Scale { get; } = new BindableFloat(4) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 2752feb0a1..a7c79aa2a0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -14,12 +14,12 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden + public class OsuModHidden : ModHidden, IMutateApproachCircles { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable), typeof(OsuModSpinIn) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index d1be162f73..6dfabed0df 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Adjusts the size of hit objects during their fade in animation. /// - public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment + public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IMutateApproachCircles { public override ModType Type => ModType.Fun; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 96ba58da23..d3ca2973f0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpinIn : ModWithVisibilityAdjustment + public class OsuModSpinIn : ModWithVisibilityAdjustment, IMutateApproachCircles { public override string Name => "Spin In"; public override string Acronym => "SI"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; // todo: this mod should be able to be compatible with hidden with a bit of further implementation. - public override Type[] IncompatibleMods => new[] { typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 4b0939db16..84263221a7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModTraceable : ModWithVisibilityAdjustment + public class OsuModTraceable : ModWithVisibilityAdjustment, IMutateApproachCircles { public override string Name => "Traceable"; public override string Acronym => "TC"; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) }; + public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { From 5cf2ac78fcc1790b2556d11336c10496c7a94da8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Jun 2021 15:40:35 +0900 Subject: [PATCH 13/24] Adjust font namespaces --- osu.Game/OsuGameBase.cs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index abf8fbc4fb..05a53452f7 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -190,29 +190,29 @@ namespace osu.Game AddFont(Resources, @"Fonts/osuFont"); - AddFont(Resources, @"Fonts/Torus-Regular"); - AddFont(Resources, @"Fonts/Torus-Light"); - AddFont(Resources, @"Fonts/Torus-SemiBold"); - AddFont(Resources, @"Fonts/Torus-Bold"); + AddFont(Resources, @"Fonts/Torus/Torus-Regular"); + AddFont(Resources, @"Fonts/Torus/Torus-Light"); + AddFont(Resources, @"Fonts/Torus/Torus-SemiBold"); + AddFont(Resources, @"Fonts/Torus/Torus-Bold"); - AddFont(Resources, @"Fonts/Inter-Regular"); - AddFont(Resources, @"Fonts/Inter-RegularItalic"); - AddFont(Resources, @"Fonts/Inter-Light"); - AddFont(Resources, @"Fonts/Inter-LightItalic"); - AddFont(Resources, @"Fonts/Inter-SemiBold"); - AddFont(Resources, @"Fonts/Inter-SemiBoldItalic"); - AddFont(Resources, @"Fonts/Inter-Bold"); - AddFont(Resources, @"Fonts/Inter-BoldItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Regular"); + AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Light"); + AddFont(Resources, @"Fonts/Inter/Inter-LightItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBold"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Bold"); + AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic"); - AddFont(Resources, @"Fonts/Noto-Basic"); - AddFont(Resources, @"Fonts/Noto-Hangul"); - AddFont(Resources, @"Fonts/Noto-CJK-Basic"); - AddFont(Resources, @"Fonts/Noto-CJK-Compatibility"); - AddFont(Resources, @"Fonts/Noto-Thai"); + AddFont(Resources, @"Fonts/Noto/Noto-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-Hangul"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility"); + AddFont(Resources, @"Fonts/Noto/Noto-Thai"); - AddFont(Resources, @"Fonts/Venera-Light"); - AddFont(Resources, @"Fonts/Venera-Bold"); - AddFont(Resources, @"Fonts/Venera-Black"); + AddFont(Resources, @"Fonts/Venera/Venera-Light"); + AddFont(Resources, @"Fonts/Venera/Venera-Bold"); + AddFont(Resources, @"Fonts/Venera/Venera-Black"); Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; From 2bf855fcca95d02d5a52746699a824ca9cebd5de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 15:45:12 +0900 Subject: [PATCH 14/24] Move all storyboard outro skip logic out of `updateCompletionState` This method should only be called to trigger the score completion portion of player progression. The storyboard skip/end logic is now handled separately in `progressToResults`. --- osu.Game/Screens/Play/Player.cs | 71 +++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9fe12c58de..c8ba554c5f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -295,12 +295,12 @@ namespace osu.Game.Screens.Play DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded => { - if (storyboardEnded.NewValue && resultsDisplayDelegate == null) - updateCompletionState(); + if (storyboardEnded.NewValue) + progressToResults(true); }; // Bind the judgement processors to ourselves - ScoreProcessor.HasCompleted.BindValueChanged(_ => updateCompletionState()); + ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged); HealthProcessor.Failed += onFail; foreach (var mod in Mods.Value.OfType()) @@ -374,7 +374,7 @@ namespace osu.Game.Screens.Play }, skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) { - RequestSkip = () => updateCompletionState(true), + RequestSkip = () => progressToResults(false), Alpha = 0 }, FailOverlay = new FailOverlay @@ -643,9 +643,8 @@ namespace osu.Game.Screens.Play /// /// Handles changes in player state which may progress the completion of gameplay / this screen's lifetime. /// - /// If in a state where a storyboard outro is to be played, offers the choice of skipping beyond it. /// Thrown if this method is called more than once without changing state. - private void updateCompletionState(bool skipStoryboardOutro = false) + private void scoreCompletionChanged(ValueChangedEvent completed) { // If this player instance is in the middle of an exit, don't attempt any kind of state update. if (!this.IsCurrentScreen()) @@ -656,7 +655,7 @@ namespace osu.Game.Screens.Play // Currently, even if this scenario is hit, prepareScoreForDisplay has already been queued (and potentially run). // In scenarios where rewinding is possible (replay, spectating) this is a non-issue as no submission/import work is done, // but it still doesn't feel right that this exists here. - if (!ScoreProcessor.HasCompleted.Value) + if (!completed.NewValue) { resultsDisplayDelegate?.Cancel(); resultsDisplayDelegate = null; @@ -667,7 +666,7 @@ namespace osu.Game.Screens.Play } if (resultsDisplayDelegate != null) - throw new InvalidOperationException(@$"{nameof(updateCompletionState)} should never be fired more than once."); + throw new InvalidOperationException(@$"{nameof(scoreCompletionChanged)} should never be fired more than once."); // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed) @@ -686,22 +685,49 @@ namespace osu.Game.Screens.Play // Asynchronously run score preparation operations (database import, online submission etc.). prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); - if (skipStoryboardOutro) - { - scheduleCompletion(); - return; - } - bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; if (storyboardHasOutro) { + // if the current beatmap has a storyboard, the progression to results will be handled by the storyboard ending + // or the user pressing the skip outro button. skipOutroOverlay.Show(); return; } - using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY)) - scheduleCompletion(); + progressToResults(true); + } + + /// + /// Queue the results screen for display. + /// + /// + /// A final display will only occur once all work is completed in . + /// + /// Whether a minimum delay () should be added before the screen is displayed. + private void progressToResults(bool withDelay) + { + if (resultsDisplayDelegate != null) + return; + + double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0; + + resultsDisplayDelegate = new ScheduledDelegate(() => + { + if (prepareScoreForDisplayTask?.IsCompleted != true) + // if the asynchronous preparation has not completed, keep repeating this delegate. + return; + + resultsDisplayDelegate?.Cancel(); + + if (!this.IsCurrentScreen()) + // This player instance may already be in the process of exiting. + return; + + this.Push(CreateResults(prepareScoreForDisplayTask.Result)); + }, Time.Current + delay, 50); + + Scheduler.Add(resultsDisplayDelegate); } private async Task prepareScoreForResults() @@ -734,7 +760,18 @@ namespace osu.Game.Screens.Play { if (!prepareScoreForDisplayTask.IsCompleted) { - scheduleCompletion(); + resultsDisplayDelegate = Schedule(() => + { + if (!prepareScoreForDisplayTask.IsCompleted) + { + scheduleCompletion(); + return; + } + + // screen may be in the exiting transition phase. + if (this.IsCurrentScreen()) + this.Push(CreateResults(prepareScoreForDisplayTask.Result)); + }); return; } From 7ef8eac7737c3fb82364fef8575eb173bff34f6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 15:56:58 +0900 Subject: [PATCH 15/24] Remove unnecessary (and no longer correct) exception --- osu.Game/Screens/Play/Player.cs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c8ba554c5f..5913e46836 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -665,9 +665,6 @@ namespace osu.Game.Screens.Play return; } - if (resultsDisplayDelegate != null) - throw new InvalidOperationException(@$"{nameof(scoreCompletionChanged)} should never be fired more than once."); - // Only show the completion screen if the player hasn't failed if (HealthProcessor.HasFailed) return; @@ -703,6 +700,7 @@ namespace osu.Game.Screens.Play /// /// /// A final display will only occur once all work is completed in . + /// This means that even after calling this method, the results screen will never be shown until ScoreProcessor.HasCompleted becomes . /// /// Whether a minimum delay () should be added before the screen is displayed. private void progressToResults(bool withDelay) @@ -756,30 +754,6 @@ namespace osu.Game.Screens.Play return Score.ScoreInfo; } - private void scheduleCompletion() => resultsDisplayDelegate = Schedule(() => - { - if (!prepareScoreForDisplayTask.IsCompleted) - { - resultsDisplayDelegate = Schedule(() => - { - if (!prepareScoreForDisplayTask.IsCompleted) - { - scheduleCompletion(); - return; - } - - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(prepareScoreForDisplayTask.Result)); - }); - return; - } - - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(prepareScoreForDisplayTask.Result)); - }); - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; #region Fail Logic From 06d1bd971cb5fdc37788efd49f8b8375074bde0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:08:49 +0900 Subject: [PATCH 16/24] Default `DrawableStoryboard` to a completed state to avoid state change on empty storyboards --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index ca041da801..8a31e4576a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -25,7 +25,7 @@ namespace osu.Game.Storyboards.Drawables /// public IBindable HasStoryboardEnded => hasStoryboardEnded; - private readonly BindableBool hasStoryboardEnded = new BindableBool(); + private readonly BindableBool hasStoryboardEnded = new BindableBool(true); protected override Container Content { get; } From 3819a1f03b510759032f5dc5e967964dcd0bab52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:12:34 +0900 Subject: [PATCH 17/24] Remove exit override behaviour I don't actually know under what scenario this could have been hit, and actually caused expected behaviour. Consider that in the scenario I describe in the comment (which I added yesterday), the user is requesting a pause or exit which would be "cancelled showing the results instead". But in such a scenario, `PerformExit` would first be run, which cancels the `resultsDisplayDelegate` in the first place. The only special case would be pressing the close button on the window decoration? Which I don't think should be a special case in the first place, so I'm just going to remove this for the time being to keep things simple. --- osu.Game/Screens/Play/Player.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5913e46836..5db9b47586 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -950,14 +950,6 @@ namespace osu.Game.Screens.Play { screenSuspension?.Expire(); - // if the results screen is prepared to be displayed, forcefully show it on an exit request. - // usually if a user has completed a play session they do want to see results. and if they don't they can hit the same key a second time. - if (resultsDisplayDelegate != null && !resultsDisplayDelegate.Cancelled && !resultsDisplayDelegate.Completed) - { - resultsDisplayDelegate.RunTask(); - return true; - } - // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. From f3426e38b422590a0f1f97ac096742254391bd9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:18:20 +0900 Subject: [PATCH 18/24] Add note about delay parameter --- osu.Game/Screens/Play/Player.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5db9b47586..f2d31e5dd2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -699,13 +699,19 @@ namespace osu.Game.Screens.Play /// Queue the results screen for display. /// /// - /// A final display will only occur once all work is completed in . - /// This means that even after calling this method, the results screen will never be shown until ScoreProcessor.HasCompleted becomes . + /// A final display will only occur once all work is completed in . This means that even after calling this method, the results screen will never be shown until ScoreProcessor.HasCompleted becomes . + /// + /// Calling this method multiple times will have no effect. /// /// Whether a minimum delay () should be added before the screen is displayed. private void progressToResults(bool withDelay) { if (resultsDisplayDelegate != null) + // Note that if progressToResults is called one withDelay=true and then withDelay=false, this no-delay timing will not be + // accounted for. shouldn't be a huge concern (a user pressing the skip button after a results progression has already been queued + // may take x00 more milliseconds than expected in the very rare edge case). + // + // If required we can handle this more correctly by rescheduling here. return; double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0; @@ -713,7 +719,7 @@ namespace osu.Game.Screens.Play resultsDisplayDelegate = new ScheduledDelegate(() => { if (prepareScoreForDisplayTask?.IsCompleted != true) - // if the asynchronous preparation has not completed, keep repeating this delegate. + // If the asynchronous preparation has not completed, keep repeating this delegate. return; resultsDisplayDelegate?.Cancel(); From 45122594e50ce23734e16be5332ee5b6603044cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:24:07 +0900 Subject: [PATCH 19/24] Remove the synchronous version of `PrepareScoreForResults` Replaces all existing usages with the `async` version. --- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 5 +++-- osu.Game/Screens/Play/Player.cs | 20 +++++-------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index cc1fb0b321..567ea6b988 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Screens; @@ -54,9 +55,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new PlaylistsResultsScreen(score, RoomId.Value.Value, PlaylistItem, true); } - protected override void PrepareScoreForResults(Score score) + protected override async Task PrepareScoreForResultsAsync(Score score) { - base.PrepareScoreForResults(score); + await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9fe12c58de..70cac9d27a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -626,7 +626,7 @@ namespace osu.Game.Screens.Play /// /// This delegate, when set, means the results screen has been queued to appear. - /// The display of the results screen may be delayed by any work being done in and . + /// The display of the results screen may be delayed by any work being done in . /// /// /// Once set, this can *only* be cancelled by rewinding, ie. if ScoreProcessor.HasCompleted becomes . @@ -706,9 +706,6 @@ namespace osu.Game.Screens.Play private async Task prepareScoreForResults() { - // ReSharper disable once MethodHasAsyncOverload - PrepareScoreForResults(Score); - try { await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); @@ -1007,22 +1004,15 @@ namespace osu.Game.Screens.Play /// /// Prepare the for display at results. /// - /// - /// This is run synchronously before is run. - /// /// The to prepare. - protected virtual void PrepareScoreForResults(Score score) + /// A task that prepares the provided score. On completion, the score is assumed to be ready for display. + protected virtual Task PrepareScoreForResultsAsync(Score score) { // perform one final population to ensure everything is up-to-date. ScoreProcessor.PopulateScore(score.ScoreInfo); - } - /// - /// Prepare the for display at results. - /// - /// The to prepare. - /// A task that prepares the provided score. On completion, the score is assumed to be ready for display. - protected virtual Task PrepareScoreForResultsAsync(Score score) => Task.CompletedTask; + return Task.CompletedTask; + } /// /// Creates the for a . From 19507e107e7185609db5b71c817e842b0cd92909 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:46:40 +0900 Subject: [PATCH 20/24] Reorder methods to make more sense --- osu.Game/Screens/Play/Player.cs | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 76f5e2336c..eab3770e27 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -695,6 +695,29 @@ namespace osu.Game.Screens.Play progressToResults(true); } + private async Task prepareScoreForResults() + { + try + { + await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Error(ex, @"Score preparation failed!"); + } + + try + { + await ImportScore(Score).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Error(ex, @"Score import failed!"); + } + + return Score.ScoreInfo; + } + /// /// Queue the results screen for display. /// @@ -734,29 +757,6 @@ namespace osu.Game.Screens.Play Scheduler.Add(resultsDisplayDelegate); } - private async Task prepareScoreForResults() - { - try - { - await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.Error(ex, @"Score preparation failed!"); - } - - try - { - await ImportScore(Score).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.Error(ex, @"Score import failed!"); - } - - return Score.ScoreInfo; - } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; #region Fail Logic From 0bc68a70181d1b09b5edbe59014d2a948a89c78b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 16:49:43 +0900 Subject: [PATCH 21/24] Move xmldoc to method --- osu.Game/Screens/Play/Player.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index eab3770e27..cadcc474b2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -679,7 +679,6 @@ namespace osu.Game.Screens.Play if (!Configuration.ShowResults) return; - // Asynchronously run score preparation operations (database import, online submission etc.). prepareScoreForDisplayTask ??= Task.Run(prepareScoreForResults); bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; @@ -695,6 +694,10 @@ namespace osu.Game.Screens.Play progressToResults(true); } + /// + /// Asynchronously run score preparation operations (database import, online submission etc.). + /// + /// The final score. private async Task prepareScoreForResults() { try From 78c5ccda605a26883268359a792c0cd5013db750 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 19:18:57 +0900 Subject: [PATCH 22/24] Fix renaming a ruleset DLL causing a startup crash --- osu.Game/Rulesets/RulesetStore.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 0a34ca9598..06bb14ce17 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.EntityFrameworkCore.Internal; using osu.Framework; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; @@ -96,13 +97,25 @@ namespace osu.Game.Rulesets context.SaveChanges(); - // add any other modes var existingRulesets = context.RulesetInfo.ToList(); + // add any other rulesets which have assemblies present but are not yet in the database. foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) { if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) - context.RulesetInfo.Add(r.RulesetInfo); + { + var existingSameShortName = existingRulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); + + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else + context.RulesetInfo.Add(r.RulesetInfo); + } } context.SaveChanges(); From 2dadc9d686071ae4e1df0e5c6859f584c28174ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 19:39:00 +0900 Subject: [PATCH 23/24] Remove unused using statement --- osu.Game/Rulesets/RulesetStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 06bb14ce17..1f12f3dfeb 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using Microsoft.EntityFrameworkCore.Internal; using osu.Framework; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; From 1ec03bf6fac74b678a543849c90062808c73b181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Jun 2021 22:25:24 +0900 Subject: [PATCH 24/24] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 490e43b5e6..54469eec43 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8eeaad1127..560e5e2493 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index db442238ce..7b3033db9c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - +