From f24569694716cbbbd92f2470680c4469f5b326bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Nov 2020 13:24:32 +0100 Subject: [PATCH 01/29] Move {-> Default}KiaiHitExplosion --- .../UI/{KiaiHitExplosion.cs => DefaultKiaiHitExplosion.cs} | 4 ++-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Rulesets.Taiko/UI/{KiaiHitExplosion.cs => DefaultKiaiHitExplosion.cs} (92%) diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs similarity index 92% rename from osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs rename to osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs index 067d390894..32c9f3ec4f 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs @@ -13,14 +13,14 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.UI { - public class KiaiHitExplosion : CircularContainer + public class DefaultKiaiHitExplosion : CircularContainer { public override bool RemoveWhenNotAlive => true; public readonly DrawableHitObject JudgedObject; private readonly HitType type; - public KiaiHitExplosion(DrawableHitObject judgedObject, HitType type) + public DefaultKiaiHitExplosion(DrawableHitObject judgedObject, HitType type) { JudgedObject = judgedObject; this.type = type; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 120cf264c3..03895dfd68 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.UI public const float DEFAULT_HEIGHT = 178; private Container hitExplosionContainer; - private Container kiaiExplosionContainer; + private Container kiaiExplosionContainer; private JudgementContainer judgementContainer; private ScrollingHitObjectContainer drumRollHitContainer; internal Drawable HitTarget; @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.UI drumRollHitContainer = new DrumRollHitContainer() } }, - kiaiExplosionContainer = new Container + kiaiExplosionContainer = new Container { Name = "Kiai hit explosions", RelativeSizeAxes = Axes.Both, @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Taiko.UI { hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); if (drawableObject.HitObject.Kiai) - kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); + kiaiExplosionContainer.Add(new DefaultKiaiHitExplosion(drawableObject, type)); } private class ProxyContainer : LifetimeManagementContainer From ed01d37966a9249bb4f81d81f03a2c01746ec374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Nov 2020 13:35:49 +0100 Subject: [PATCH 02/29] Reintroduce KiaiHitExplosion as skinnable --- .../TaikoSkinComponents.cs | 1 + .../UI/DefaultKiaiHitExplosion.cs | 9 +--- .../UI/KiaiHitExplosion.cs | 47 +++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 6 +-- 4 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 132d8f8868..bf48898dd2 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko TaikoExplosionMiss, TaikoExplosionOk, TaikoExplosionGreat, + TaikoExplosionKiai, Scroller, Mascot, } diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs index 32c9f3ec4f..7ce8b016d5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.UI @@ -17,19 +16,13 @@ namespace osu.Game.Rulesets.Taiko.UI { public override bool RemoveWhenNotAlive => true; - public readonly DrawableHitObject JudgedObject; private readonly HitType type; - public DefaultKiaiHitExplosion(DrawableHitObject judgedObject, HitType type) + public DefaultKiaiHitExplosion(HitType type) { - JudgedObject = judgedObject; this.type = type; - Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1); Blending = BlendingParameters.Additive; diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs new file mode 100644 index 0000000000..20900a9352 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class KiaiHitExplosion : Container + { + public override bool RemoveWhenNotAlive => true; + + [Cached(typeof(DrawableHitObject))] + public readonly DrawableHitObject JudgedObject; + + private readonly HitType hitType; + + private SkinnableDrawable skinnable; + + public override double LifetimeStart => skinnable.Drawable.LifetimeStart; + + public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd; + + public KiaiHitExplosion(DrawableHitObject judgedObject, HitType hitType) + { + JudgedObject = judgedObject; + this.hitType = hitType; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1); + } + + [BackgroundDependencyLoader] + private void load() + { + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoExplosionKiai), _ => new DefaultKiaiHitExplosion(hitType)); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 03895dfd68..120cf264c3 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.UI public const float DEFAULT_HEIGHT = 178; private Container hitExplosionContainer; - private Container kiaiExplosionContainer; + private Container kiaiExplosionContainer; private JudgementContainer judgementContainer; private ScrollingHitObjectContainer drumRollHitContainer; internal Drawable HitTarget; @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.UI drumRollHitContainer = new DrumRollHitContainer() } }, - kiaiExplosionContainer = new Container + kiaiExplosionContainer = new Container { Name = "Kiai hit explosions", RelativeSizeAxes = Axes.Both, @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Taiko.UI { hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); if (drawableObject.HitObject.Kiai) - kiaiExplosionContainer.Add(new DefaultKiaiHitExplosion(drawableObject, type)); + kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); } private class ProxyContainer : LifetimeManagementContainer From 35763a74fd75c18897d7236b4f58f2218c80135e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Nov 2020 14:07:39 +0100 Subject: [PATCH 03/29] Add test scene --- .../Skinning/TestSceneKiaiHitExplosion.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneKiaiHitExplosion.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneKiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneKiaiHitExplosion.cs new file mode 100644 index 0000000000..b558709592 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneKiaiHitExplosion.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests.Skinning +{ + [TestFixture] + public class TestSceneKiaiHitExplosion : TaikoSkinnableTestScene + { + [Test] + public void TestKiaiHits() + { + AddStep("rim hit", () => SetContents(() => getContentFor(createHit(HitType.Rim)))); + AddStep("centre hit", () => SetContents(() => getContentFor(createHit(HitType.Centre)))); + } + + private Drawable getContentFor(DrawableTestHit hit) + { + return new Container + { + RelativeSizeAxes = Axes.Both, + Child = new KiaiHitExplosion(hit, hit.HitObject.Type) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + + private DrawableTestHit createHit(HitType type) => new DrawableTestHit(new Hit { StartTime = Time.Current, Type = type }); + } +} From 0387d994bdc6136259d32c3b2181842a6e91f8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Nov 2020 14:08:27 +0100 Subject: [PATCH 04/29] Do not lookup default kiai explosion if skin has own --- .../Skinning/TaikoLegacySkinTransformer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index c88480d18f..ddbf20b827 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -114,6 +114,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; + case TaikoSkinComponents.TaikoExplosionKiai: + // suppress the default kiai explosion if the skin brings its own sprites. + if (hasExplosion.Value) + return Drawable.Empty(); + + return null; + case TaikoSkinComponents.Scroller: if (GetTexture("taiko-slider") != null) return new LegacyTaikoScroller(); From be4735cd2ba8f37e853d9a09796d1248cbddc8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 10 Nov 2020 14:50:19 +0100 Subject: [PATCH 05/29] Explicitly set lifetime to ensure empty drawables are cleaned up --- .../Skinning/TaikoLegacySkinTransformer.cs | 3 ++- osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index ddbf20b827..880af3fbd8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -116,8 +116,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionKiai: // suppress the default kiai explosion if the skin brings its own sprites. + // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. if (hasExplosion.Value) - return Drawable.Empty(); + return KiaiHitExplosion.EmptyExplosion(); return null; diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index 20900a9352..326cb23897 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -43,5 +43,11 @@ namespace osu.Game.Rulesets.Taiko.UI { Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoExplosionKiai), _ => new DefaultKiaiHitExplosion(hitType)); } + + /// + /// Helper function to use when an explosion is not desired. + /// Lifetime is set to avoid accumulating empty drawables in the parent container. + /// + public static Drawable EmptyExplosion() => Empty().With(d => d.LifetimeEnd = double.MinValue); } } From 1173ef089099e2468b80cf33eeb8f9bf79bbcc80 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Nov 2020 12:37:00 +0900 Subject: [PATCH 06/29] Fix mania notelock crashing with overlapping hitwindows --- .../TestSceneHoldNoteInput.cs | 2 +- .../TestSceneOutOfOrderHits.cs | 23 +++++++++++++++++++ .../Objects/Drawables/DrawableHoldNote.cs | 2 +- .../UI/OrderedHitPolicy.cs | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 5cb1519196..6c9f184c2c 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests assertHeadJudgement(HitResult.Miss); assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); - assertNoteJudgement(HitResult.IgnoreHit); + assertNoteJudgement(HitResult.IgnoreMiss); } /// diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index e8c2472c3b..d699921307 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -54,6 +54,29 @@ namespace osu.Game.Rulesets.Mania.Tests } } + [Test] + public void TestMissAfterNextObjectStartTime() + { + var objects = new List + { + new HoldNote + { + StartTime = 1000, + EndTime = 1200, + }, + new HoldNote + { + StartTime = 1220, + EndTime = 1420 + } + }; + + performTest(objects, new List()); + + addJudgementAssert(objects[0], HitResult.IgnoreMiss); + addJudgementAssert(objects[1], HitResult.IgnoreMiss); + } + private void addJudgementAssert(ManiaHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index d9d740c145..3b3f72157a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (Tail.AllJudged) { - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); endHold(); } diff --git a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs index 0f9cd48dd8..9bc577a81e 100644 --- a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.UI /// The that was hit. public void HandleHit(DrawableHitObject hitObject) { - if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) + if (hitObject.IsHit && !IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) From 626231d90626790a063f9b93256276e2b4d8135e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Nov 2020 12:53:32 +0900 Subject: [PATCH 07/29] Completely remove check as it can occur for hits too --- .../TestSceneOutOfOrderHits.cs | 51 +++++++++++++++++-- .../UI/OrderedHitPolicy.cs | 4 -- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index d699921307..86a142f2f6 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.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 System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -55,19 +56,19 @@ namespace osu.Game.Rulesets.Mania.Tests } [Test] - public void TestMissAfterNextObjectStartTime() + public void TestHoldNoteMissAfterNextObjectStartTime() { var objects = new List { new HoldNote { StartTime = 1000, - EndTime = 1200, + EndTime = 1010, }, new HoldNote { - StartTime = 1220, - EndTime = 1420 + StartTime = 1020, + EndTime = 1030 } }; @@ -77,12 +78,54 @@ namespace osu.Game.Rulesets.Mania.Tests addJudgementAssert(objects[1], HitResult.IgnoreMiss); } + [Test] + public void TestHoldNoteReleasedHitAfterNextObjectStartTime() + { + var objects = new List + { + new HoldNote + { + StartTime = 1000, + EndTime = 1010, + }, + new HoldNote + { + StartTime = 1020, + EndTime = 1030 + } + }; + + var frames = new List + { + new ManiaReplayFrame(1000, ManiaAction.Key1), + new ManiaReplayFrame(1030), + new ManiaReplayFrame(1040, ManiaAction.Key1), + new ManiaReplayFrame(1050) + }; + + performTest(objects, frames); + + addJudgementAssert(objects[0], HitResult.IgnoreHit); + addJudgementAssert("first head", () => ((HoldNote)objects[0]).Head, HitResult.Perfect); + addJudgementAssert("first tail", () => ((HoldNote)objects[0]).Tail, HitResult.Perfect); + + addJudgementAssert(objects[1], HitResult.IgnoreHit); + addJudgementAssert("second head", () => ((HoldNote)objects[1]).Head, HitResult.Great); + addJudgementAssert("second tail", () => ((HoldNote)objects[1]).Tail, HitResult.Perfect); + } + private void addJudgementAssert(ManiaHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); } + private void addJudgementAssert(string name, Func hitObject, HitResult result) + { + AddAssert($"{name} judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); + } + private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", diff --git a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs index 9bc577a81e..961858b62b 100644 --- a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.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; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Mania.Objects.Drawables; @@ -44,9 +43,6 @@ namespace osu.Game.Rulesets.Mania.UI /// The that was hit. public void HandleHit(DrawableHitObject hitObject) { - if (hitObject.IsHit && !IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) - throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); - foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) { if (obj.Judged) From 508ae91a978e3d706d654457cbfaff4206aea266 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Nov 2020 12:53:53 +0900 Subject: [PATCH 08/29] Revert unnecessary change --- osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs | 2 +- osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs | 4 ++-- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 6c9f184c2c..5cb1519196 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests assertHeadJudgement(HitResult.Miss); assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); - assertNoteJudgement(HitResult.IgnoreMiss); + assertNoteJudgement(HitResult.IgnoreHit); } /// diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index 86a142f2f6..cecac38f70 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -74,8 +74,8 @@ namespace osu.Game.Rulesets.Mania.Tests performTest(objects, new List()); - addJudgementAssert(objects[0], HitResult.IgnoreMiss); - addJudgementAssert(objects[1], HitResult.IgnoreMiss); + addJudgementAssert(objects[0], HitResult.IgnoreHit); + addJudgementAssert(objects[1], HitResult.IgnoreHit); } [Test] diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 3b3f72157a..d9d740c145 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (Tail.AllJudged) { - ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(r => r.Type = r.Judgement.MaxResult); endHold(); } From c308eb75d991d53cdd28b99968b05e3ee8c3762e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Nov 2020 14:45:50 +0900 Subject: [PATCH 09/29] Move logic for performing actions from specific screen to its own component --- osu.Game/OsuGame.cs | 31 +------ osu.Game/PerformFromMenuRunner.cs | 145 ++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 osu.Game/PerformFromMenuRunner.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5119f262d5..cbf463d3bc 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -463,7 +463,7 @@ namespace osu.Game #endregion - private ScheduledDelegate performFromMainMenuTask; + private PerformFromMenuRunner performFromMainMenuTask; /// /// Perform an action only after returning to a specific screen as indicated by . @@ -474,34 +474,7 @@ namespace osu.Game public void PerformFromScreen(Action action, IEnumerable validScreens = null) { performFromMainMenuTask?.Cancel(); - - validScreens ??= Enumerable.Empty(); - validScreens = validScreens.Append(typeof(MainMenu)); - - CloseAllOverlays(false); - - // we may already be at the target screen type. - if (validScreens.Contains(ScreenStack.CurrentScreen?.GetType()) && !Beatmap.Disabled) - { - action(ScreenStack.CurrentScreen); - return; - } - - // find closest valid target - IScreen screen = ScreenStack.CurrentScreen; - - while (screen != null) - { - if (validScreens.Contains(screen.GetType())) - { - screen.MakeCurrent(); - break; - } - - screen = screen.GetParentScreen(); - } - - performFromMainMenuTask = Schedule(() => PerformFromScreen(action, validScreens)); + Add(performFromMainMenuTask = new PerformFromMenuRunner(action, validScreens, () => ScreenStack.CurrentScreen)); } /// diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs new file mode 100644 index 0000000000..8fcea7c277 --- /dev/null +++ b/osu.Game/PerformFromMenuRunner.cs @@ -0,0 +1,145 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; +using osu.Game.Overlays.Notifications; +using osu.Game.Screens.Menu; + +namespace osu.Game +{ + internal class PerformFromMenuRunner : Component + { + private readonly Action finalAction; + private readonly IEnumerable validScreens; + private readonly Func getCurrentScreen; + + [Resolved] + private NotificationOverlay notifications { get; set; } + + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + private readonly ScheduledDelegate task; + + private PopupDialog lastEncounteredDialog; + private IScreen lastEncounteredDialogScreen; + + /// + /// Perform an action only after returning to a specific screen as indicated by . + /// Eagerly tries to exit the current screen until it succeeds. + /// + /// The action to perform once we are in the correct state. + /// An optional collection of valid screen types. If any of these screens are already current we can perform the action immediately, else the first valid parent will be made current before performing the action. is used if not specified. + /// A function to retrieve the currently displayed game screen. + public PerformFromMenuRunner(Action finalAction, IEnumerable validScreens, Func getCurrentScreen) + { + validScreens ??= Enumerable.Empty(); + validScreens = validScreens.Append(typeof(MainMenu)); + + this.finalAction = finalAction; + this.validScreens = validScreens; + this.getCurrentScreen = getCurrentScreen; + + Scheduler.Add(task = new ScheduledDelegate(checkCanComplete, 0, 200)); + } + + /// + /// Cancel this runner from running. + /// + public void Cancel() + { + task.Cancel(); + Expire(); + } + + private void checkCanComplete() + { + // find closest valid target + IScreen current = getCurrentScreen(); + + // a dialog may be blocking the execution for now. + if (checkForDialog(current)) return; + + // we may already be at the target screen type. + if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled) + { + complete(); + return; + } + + game.CloseAllOverlays(false); + + while (current != null) + { + if (validScreens.Contains(current.GetType())) + { + current.MakeCurrent(); + break; + } + + current = current.GetParentScreen(); + } + } + + /// + /// Check whether there is currently a dialog requiring user interaction. + /// + /// + /// Whether a dialog blocked interaction. + private bool checkForDialog(IScreen current) + { + var currentDialog = dialogOverlay.CurrentDialog; + + if (lastEncounteredDialog != null) + { + if (lastEncounteredDialog == currentDialog) + // still waiting on user interaction + return true; + + if (lastEncounteredDialogScreen != current) + { + // a dialog was previously encountered but has since been dismissed. + // if the screen changed, the user likely confirmed an exit dialog and we should continue attempting the action. + lastEncounteredDialog = null; + lastEncounteredDialogScreen = null; + return false; + } + + // the last dialog encountered has been dismissed but the screen has not changed, abort. + Cancel(); + notifications.Post(new SimpleNotification { Text = @"An action was interrupted due to a dialog being displayed." }); + return true; + } + + if (currentDialog == null) + return false; + + // a new dialog was encountered. + lastEncounteredDialog = currentDialog; + lastEncounteredDialogScreen = current; + return true; + } + + private void complete() + { + finalAction(getCurrentScreen()); + Cancel(); + } + } +} From 804450e707300b51abfc13014d354f9a9a1123ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Nov 2020 15:49:45 +0900 Subject: [PATCH 10/29] Remove duplicate instantiation of externalLinkOpener --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5119f262d5..a4e7214d32 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -662,7 +662,6 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); - loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add); chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; From b28a0d5cd50333437de4af21ed05c37466eabe06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Nov 2020 15:58:43 +0900 Subject: [PATCH 11/29] Add test coverage --- .../Visual/Navigation/OsuGameTestScene.cs | 2 +- .../Navigation/TestScenePerformFromScreen.cs | 130 ++++++++++++++++-- 2 files changed, 116 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 4c18cfa61c..c5038068ec 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Navigation } [SetUpSteps] - public void SetUpSteps() + public virtual void SetUpSteps() { AddStep("Create new game instance", () => { diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 75c6a2b733..a4190e0b84 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -2,23 +2,33 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Overlays; +using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osu.Game.Screens.Select; +using osuTK.Input; namespace osu.Game.Tests.Visual.Navigation { public class TestScenePerformFromScreen : OsuGameTestScene { + private bool actionPerformed; + + public override void SetUpSteps() + { + AddStep("reset status", () => actionPerformed = false); + + base.SetUpSteps(); + } + [Test] public void TestPerformAtMenu() { - AddAssert("could perform immediately", () => - { - bool actionPerformed = false; - Game.PerformFromScreen(_ => actionPerformed = true); - return actionPerformed; - }); + AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true)); + AddAssert("did perform", () => actionPerformed); } [Test] @@ -26,12 +36,9 @@ namespace osu.Game.Tests.Visual.Navigation { PushAndConfirm(() => new PlaySongSelect()); - AddAssert("could perform immediately", () => - { - bool actionPerformed = false; - Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }); - return actionPerformed; - }); + AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) })); + AddAssert("did perform", () => actionPerformed); + AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); } [Test] @@ -39,7 +46,6 @@ namespace osu.Game.Tests.Visual.Navigation { PushAndConfirm(() => new PlaySongSelect()); - bool actionPerformed = false; AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu); AddAssert("did perform", () => actionPerformed); @@ -51,7 +57,6 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => new PlaySongSelect()); PushAndConfirm(() => new PlayerLoader(() => new Player())); - bool actionPerformed = false; AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) })); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); AddAssert("did perform", () => actionPerformed); @@ -63,10 +68,105 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => new PlaySongSelect()); PushAndConfirm(() => new PlayerLoader(() => new Player())); - bool actionPerformed = false; AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu); AddAssert("did perform", () => actionPerformed); } + + [TestCase(true)] + [TestCase(false)] + public void TestPerformBlockedByDialog(bool confirmed) + { + DialogBlockingScreen blocker = null; + + PushAndConfirm(() => blocker = new DialogBlockingScreen()); + AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); + + AddWaitStep("wait a bit", 10); + + AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen); + AddAssert("did not perform", () => !actionPerformed); + AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1); + + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); + + if (confirmed) + { + AddStep("accept dialog", () => InputManager.Key(Key.Number1)); + AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get().CurrentDialog == null); + AddUntilStep("did perform", () => actionPerformed); + } + else + { + AddStep("cancel dialog", () => InputManager.Key(Key.Number2)); + AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen); + AddAssert("did not perform", () => !actionPerformed); + } + } + + [TestCase(true)] + [TestCase(false)] + public void TestPerformBlockedByDialogNested(bool confirmSecond) + { + DialogBlockingScreen blocker = null; + DialogBlockingScreen blocker2 = null; + + PushAndConfirm(() => blocker = new DialogBlockingScreen()); + PushAndConfirm(() => blocker2 = new DialogBlockingScreen()); + + AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); + + AddUntilStep("wait for dialog", () => blocker2.ExitAttempts == 1); + + AddWaitStep("wait a bit", 10); + + AddUntilStep("wait for dialog display", () => Game.Dependencies.Get().IsLoaded); + + AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker2); + AddAssert("did not perform", () => !actionPerformed); + AddAssert("only one exit attempt", () => blocker2.ExitAttempts == 1); + + AddStep("accept dialog", () => InputManager.Key(Key.Number1)); + AddUntilStep("screen changed", () => Game.ScreenStack.CurrentScreen == blocker); + + AddUntilStep("wait for second dialog", () => blocker.ExitAttempts == 1); + AddAssert("did not perform", () => !actionPerformed); + AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1); + + if (confirmSecond) + { + AddStep("accept dialog", () => InputManager.Key(Key.Number1)); + AddUntilStep("did perform", () => actionPerformed); + } + else + { + AddStep("cancel dialog", () => InputManager.Key(Key.Number2)); + AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker); + AddAssert("did not perform", () => !actionPerformed); + } + } + + public class DialogBlockingScreen : OsuScreen + { + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + private int dialogDisplayCount; + + public int ExitAttempts { get; private set; } + + public override bool OnExiting(IScreen next) + { + ExitAttempts++; + + if (dialogDisplayCount++ < 1) + { + dialogOverlay.Push(new ConfirmExitDialog(this.Exit, () => { })); + return true; + } + + return base.OnExiting(next); + } + } } } From 5d55af58182449a73da8aea2c5da743fb86fc589 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Nov 2020 16:35:48 +0900 Subject: [PATCH 12/29] Fix hitobjects sometimes not fading in completely with HD mod --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 10 ++++++++-- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 025e202666..cf7faca9b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -27,17 +27,23 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var d in drawables) - d.ApplyCustomUpdateState += applyFadeInAdjustment; + { + d.HitObjectApplied += applyFadeInAdjustment; + applyFadeInAdjustment(d); + } base.ApplyToDrawableHitObjects(drawables); } - private void applyFadeInAdjustment(DrawableHitObject hitObject, ArmedState state) + private void applyFadeInAdjustment(DrawableHitObject hitObject) { if (!(hitObject is DrawableOsuHitObject d)) return; d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier; + + foreach (var nested in d.NestedHitObjects) + applyFadeInAdjustment(nested); } private double lastSliderHeadFadeOutStartTime; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7a4e136553..62709b2900 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -26,8 +26,16 @@ namespace osu.Game.Rulesets.Objects.Drawables [Cached(typeof(DrawableHitObject))] public abstract class DrawableHitObject : SkinReloadableDrawable { + /// + /// Invoked after this 's applied has had its defaults applied. + /// public event Action DefaultsApplied; + /// + /// Invoked after a has been applied to this . + /// + public event Action HitObjectApplied; + /// /// The currently represented by this . /// @@ -192,6 +200,7 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject.DefaultsApplied += onDefaultsApplied; OnApply(hitObject); + HitObjectApplied?.Invoke(this); // If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates. if (IsLoaded) From 891218ec6b754ef13165c9e95dc88b64d66d9c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Nov 2020 09:11:33 +0100 Subject: [PATCH 13/29] Inline empty explosion in legacy transformer --- .../Skinning/TaikoLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 880af3fbd8..96fb065e79 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning // suppress the default kiai explosion if the skin brings its own sprites. // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. if (hasExplosion.Value) - return KiaiHitExplosion.EmptyExplosion(); + return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue); return null; diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index 326cb23897..20900a9352 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -43,11 +43,5 @@ namespace osu.Game.Rulesets.Taiko.UI { Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoExplosionKiai), _ => new DefaultKiaiHitExplosion(hitType)); } - - /// - /// Helper function to use when an explosion is not desired. - /// Lifetime is set to avoid accumulating empty drawables in the parent container. - /// - public static Drawable EmptyExplosion() => Empty().With(d => d.LifetimeEnd = double.MinValue); } } From a08833f3b3cd3b7581be8e95a0b56d03611c8567 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Nov 2020 18:03:04 +0900 Subject: [PATCH 14/29] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index bbe8426316..5078fee1cf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8f0cc58594..405fb1a6ca 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f766e0ec03..099ecd8319 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 85017a009428ecf9a3ee881a28b209ea1e07b207 Mon Sep 17 00:00:00 2001 From: kamp Date: Wed, 11 Nov 2020 20:20:29 +0100 Subject: [PATCH 15/29] Add test for accuracy heatmap to TestCaseStatisticsPanel --- .../Ranking/TestSceneStatisticsPanel.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 8700fbeb42..6b0ff4b9c9 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -1,19 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Tests.Visual.Ranking { public class TestSceneStatisticsPanel : OsuTestScene { [Test] - public void TestScoreWithStatistics() + public void TestScoreWithTimeStatistics() { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { @@ -23,6 +28,17 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(score); } + [Test] + public void TestScoreWithPositionStatistics() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) + { + HitEvents = CreatePositionDistributedHitEvents() + }; + + loadPanel(score); + } + [Test] public void TestScoreWithoutStatistics() { @@ -44,5 +60,24 @@ namespace osu.Game.Tests.Visual.Ranking Score = { Value = score } }; }); + + public static List CreatePositionDistributedHitEvents() + { + var hitEvents = new List(); + // Use constant seed for reproducibility + var random = new Random(0); + + for (int i = 0; i < 500; i++) + { + float angle = (float) random.NextDouble() * 2 * (float) Math.PI; + float radius = (float) random.NextDouble() * 0.5f * HitCircle.OBJECT_RADIUS; + + Vector2 position = new Vector2(radius * (float) Math.Cos(angle), radius * (float) Math.Sin(angle)); + + hitEvents.Add(new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), position)); + } + + return hitEvents; + } } } From 8341d3ad741f0e7e47d86f76c0566981b241d632 Mon Sep 17 00:00:00 2001 From: kamp Date: Wed, 11 Nov 2020 21:52:43 +0100 Subject: [PATCH 16/29] Fix formatting --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 6b0ff4b9c9..3185b782db 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -69,10 +69,10 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 500; i++) { - float angle = (float) random.NextDouble() * 2 * (float) Math.PI; - float radius = (float) random.NextDouble() * 0.5f * HitCircle.OBJECT_RADIUS; + float angle = (float)random.NextDouble() * 2 * (float)Math.PI; + float radius = (float)random.NextDouble() * 0.5f * HitCircle.OBJECT_RADIUS; - Vector2 position = new Vector2(radius * (float) Math.Cos(angle), radius * (float) Math.Sin(angle)); + Vector2 position = new Vector2(radius * (float)Math.Cos(angle), radius * (float)Math.Sin(angle)); hitEvents.Add(new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), position)); } From 423f0fbda74313fdef0a8c97691111826895bce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Nov 2020 22:37:15 +0100 Subject: [PATCH 17/29] Reference constant through base class --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 3185b782db..89a82b01a8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 500; i++) { float angle = (float)random.NextDouble() * 2 * (float)Math.PI; - float radius = (float)random.NextDouble() * 0.5f * HitCircle.OBJECT_RADIUS; + float radius = (float)random.NextDouble() * 0.5f * OsuHitObject.OBJECT_RADIUS; Vector2 position = new Vector2(radius * (float)Math.Cos(angle), radius * (float)Math.Sin(angle)); From 1984a9f70d6e6c17f4d547ffac091b74ccddc9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Nov 2020 22:40:52 +0100 Subject: [PATCH 18/29] Reduce amount of casting --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 89a82b01a8..e92ae7c538 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -69,10 +69,10 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 500; i++) { - float angle = (float)random.NextDouble() * 2 * (float)Math.PI; - float radius = (float)random.NextDouble() * 0.5f * OsuHitObject.OBJECT_RADIUS; + double angle = random.NextDouble() * 2 * Math.PI; + double radius = random.NextDouble() * 0.5f * OsuHitObject.OBJECT_RADIUS; - Vector2 position = new Vector2(radius * (float)Math.Cos(angle), radius * (float)Math.Sin(angle)); + var position = new Vector2((float)(radius * Math.Cos(angle)), (float)(radius * Math.Sin(angle))); hitEvents.Add(new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), position)); } From 1426530496bf7b1d9ec35a365a30fdf2839c7356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Nov 2020 22:41:25 +0100 Subject: [PATCH 19/29] Make method private --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index e92ae7c538..d91aec753c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { - HitEvents = CreatePositionDistributedHitEvents() + HitEvents = createPositionDistributedHitEvents() }; loadPanel(score); @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Ranking }; }); - public static List CreatePositionDistributedHitEvents() + private static List createPositionDistributedHitEvents() { var hitEvents = new List(); // Use constant seed for reproducibility From f753f138c6bb3df154e109d30ee6bbbaaa841257 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 12 Nov 2020 03:11:29 +0300 Subject: [PATCH 20/29] Add counter to most played beatmaps section in user overlay --- .../Historical/PaginatedMostPlayedBeatmapContainer.cs | 4 +++- osu.Game/Users/User.cs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 8f19cd900c..556f3139dd 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "Most Played Beatmaps", "No records. :(") + : base(user, "Most Played Beatmaps", "No records. :(", CounterVisibilityState.AlwaysVisible) { ItemsPerPage = 5; } @@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical ItemsContainer.Direction = FillDirection.Vertical; } + protected override int GetCount(User user) => user.BeatmapPlaycountsCount; + protected override APIRequest> CreateRequest() => new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 2a76a963e1..d7e78d5b35 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -144,6 +144,9 @@ namespace osu.Game.Users [JsonProperty(@"scores_first_count")] public int ScoresFirstCount; + [JsonProperty(@"beatmap_playcounts_count")] + public int BeatmapPlaycountsCount; + [JsonProperty] private string[] playstyle { From ad79c2bc6286c377dd2c8757abb7fa18a7f81454 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Nov 2020 10:55:22 +0900 Subject: [PATCH 21/29] Avoid multiple enumeration by converting to array at construction time --- osu.Game/PerformFromMenuRunner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 8fcea7c277..9afe87f74f 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -20,7 +20,7 @@ namespace osu.Game internal class PerformFromMenuRunner : Component { private readonly Action finalAction; - private readonly IEnumerable validScreens; + private readonly Type[] validScreens; private readonly Func getCurrentScreen; [Resolved] @@ -53,7 +53,7 @@ namespace osu.Game validScreens = validScreens.Append(typeof(MainMenu)); this.finalAction = finalAction; - this.validScreens = validScreens; + this.validScreens = validScreens.ToArray(); this.getCurrentScreen = getCurrentScreen; Scheduler.Add(task = new ScheduledDelegate(checkCanComplete, 0, 200)); From 25af091409142d702041f5a5bd15bea093c78897 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Nov 2020 17:03:42 +0900 Subject: [PATCH 22/29] Fix storyboard animations of very old beatmaps playing too slow Closes https://github.com/ppy/osu/issues/10772. --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 ++ osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 12 +++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 442be6e837..37ab489da5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -16,8 +16,6 @@ namespace osu.Game.Beatmaps.Formats { public class LegacyBeatmapDecoder : LegacyDecoder { - public const int LATEST_VERSION = 14; - private Beatmap beatmap; private ConvertHitObjectParser parser; diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 7b377e481f..de4dc8cdc8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -16,6 +16,8 @@ namespace osu.Game.Beatmaps.Formats public abstract class LegacyDecoder : Decoder where T : new() { + public const int LATEST_VERSION = 14; + protected readonly int FormatVersion; protected LegacyDecoder(int version) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 8d8ca523d5..9a244c8bb2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; @@ -23,15 +24,15 @@ namespace osu.Game.Beatmaps.Formats private readonly Dictionary variables = new Dictionary(); - public LegacyStoryboardDecoder() - : base(0) + public LegacyStoryboardDecoder(int version = LATEST_VERSION) + : base(version) { } public static void Register() { // note that this isn't completely correct - AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder()); + AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder(Parsing.ParseInt(m.Split('v').Last()))); AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); SetFallbackDecoder(() => new LegacyStoryboardDecoder()); } @@ -133,6 +134,11 @@ namespace osu.Game.Beatmaps.Formats var y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE); var frameCount = Parsing.ParseInt(split[6]); var frameDelay = Parsing.ParseDouble(split[7]); + + if (FormatVersion < 6) + // this is random as hell but taken straight from osu-stable. + frameDelay = Math.Round(0.015 * frameDelay) * 1.186 * (1000 / 60f); + var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); storyboard.GetLayer(layer).Add(storyboardSprite); From 0ae6f8229171c215bf441385e6d80287eecf096a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Nov 2020 21:46:58 +0100 Subject: [PATCH 23/29] Fix incorrect fade of slider ends after a rewind --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 46 +++++++++++++--------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index cf7faca9b9..84a335750a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -46,9 +47,6 @@ namespace osu.Game.Rulesets.Osu.Mods applyFadeInAdjustment(nested); } - private double lastSliderHeadFadeOutStartTime; - private double lastSliderHeadFadeOutDuration; - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { base.ApplyIncreasedVisibilityState(hitObject, state); @@ -78,33 +76,24 @@ namespace osu.Game.Rulesets.Osu.Mods { case DrawableSliderTail sliderTail: // use stored values from head circle to achieve same fade sequence. - fadeOutDuration = lastSliderHeadFadeOutDuration; - fadeOutStartTime = lastSliderHeadFadeOutStartTime; + var tailFadeOutParameters = getFadeOutParametersFromSliderHead(h); - using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) - sliderTail.FadeOut(fadeOutDuration); + using (drawable.BeginAbsoluteSequence(tailFadeOutParameters.startTime, true)) + sliderTail.FadeOut(tailFadeOutParameters.duration); break; case DrawableSliderRepeat sliderRepeat: // use stored values from head circle to achieve same fade sequence. - fadeOutDuration = lastSliderHeadFadeOutDuration; - fadeOutStartTime = lastSliderHeadFadeOutStartTime; + var repeatFadeOutParameters = getFadeOutParametersFromSliderHead(h); - using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) + using (drawable.BeginAbsoluteSequence(repeatFadeOutParameters.startTime, true)) // only apply to circle piece – reverse arrow is not affected by hidden. - sliderRepeat.CirclePiece.FadeOut(fadeOutDuration); + sliderRepeat.CirclePiece.FadeOut(repeatFadeOutParameters.duration); break; case DrawableHitCircle circle: - - if (circle is DrawableSliderHead) - { - lastSliderHeadFadeOutDuration = fadeOutDuration; - lastSliderHeadFadeOutStartTime = fadeOutStartTime; - } - Drawable fadeTarget = circle; if (increaseVisibility) @@ -125,6 +114,8 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSlider slider: + associateNestedSliderCirclesWithHead(slider.HitObject); + using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) slider.Body.FadeOut(longFadeDuration, Easing.Out); @@ -149,5 +140,24 @@ namespace osu.Game.Rulesets.Osu.Mods break; } } + + private readonly Dictionary correspondingSliderHeadForObject = new Dictionary(); + + private void associateNestedSliderCirclesWithHead(Slider slider) + { + var sliderHead = slider.NestedHitObjects.Single(obj => obj is SliderHeadCircle); + + foreach (var nested in slider.NestedHitObjects) + { + if ((nested is SliderRepeat || nested is SliderEndCircle) && !correspondingSliderHeadForObject.ContainsKey(nested)) + correspondingSliderHeadForObject[nested] = (SliderHeadCircle)sliderHead; + } + } + + private (double startTime, double duration) getFadeOutParametersFromSliderHead(OsuHitObject h) + { + var sliderHead = correspondingSliderHeadForObject[h]; + return (sliderHead.StartTime - sliderHead.TimePreempt + sliderHead.TimeFadeIn, sliderHead.TimePreempt * fade_out_duration_multiplier); + } } } From de31c1ea0cd3df825d65fae00dc6b644d3f16a9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Nov 2020 17:58:39 +0900 Subject: [PATCH 24/29] Fix skinfallback test crashing on repeats --- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 075bf314bc..856bfd7e80 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -125,6 +125,9 @@ namespace osu.Game.Rulesets.Osu.Tests { if (!enabled) return null; + if (component is OsuSkinComponent osuComponent && osuComponent.Component == OsuSkinComponents.SliderBody) + return null; + return new OsuSpriteText { Text = identifier, From 261ddd2b4a687c12245a963653aea3e76ff6e724 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Nov 2020 18:48:25 +0900 Subject: [PATCH 25/29] Fix samples not being disposed --- .../Objects/Drawables/DrawableHitObject.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 244cf831c3..6af445939b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Logging; using osu.Framework.Threading; @@ -126,6 +127,8 @@ namespace osu.Game.Rulesets.Objects.Drawables [CanBeNull] private HitObjectLifetimeEntry lifetimeEntry; + private Container samplesContainer; + /// /// Creates a new . /// @@ -142,6 +145,9 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load(OsuConfigManager config) { config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); + + // Explicit non-virtual function call. + base.AddInternal(samplesContainer = new Container { RelativeSizeAxes = Axes.Both }); } protected override void LoadAsyncComplete() @@ -296,11 +302,8 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected virtual void LoadSamples() { - if (Samples != null) - { - RemoveInternal(Samples); - Samples = null; - } + samplesContainer.Clear(); + Samples = null; var samples = GetSamples().ToArray(); @@ -313,8 +316,7 @@ namespace osu.Game.Rulesets.Objects.Drawables + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } - Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); - AddInternal(Samples); + samplesContainer.Add(Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)))); } private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples(); From 1439c0f39289d7d5a3d8848a2fb2f2bd86d78783 Mon Sep 17 00:00:00 2001 From: kamp Date: Thu, 12 Nov 2020 23:19:29 +0100 Subject: [PATCH 26/29] Prevent SelectionBox handles from appearing when a stack of circles is selected --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index a72dcff1e9..fd7ea050b9 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -21,12 +21,12 @@ namespace osu.Game.Rulesets.Osu.Edit { base.OnSelectionChanged(); - bool canOperate = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); + Quad quad = selectedMovableObjects.Length > 0 ? getSurroundingQuad(selectedMovableObjects) : new Quad(); - SelectionBox.CanRotate = canOperate; - SelectionBox.CanScaleX = canOperate; - SelectionBox.CanScaleY = canOperate; - SelectionBox.CanReverse = canOperate; + SelectionBox.CanRotate = quad.Width > 0 || quad.Height > 0; + SelectionBox.CanScaleX = quad.Width > 0; + SelectionBox.CanScaleY = quad.Height > 0; + SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } protected override void OnOperationEnded() From 35329aa976747372fdfaa05079557b3abcfaecf4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 14:33:23 +0900 Subject: [PATCH 27/29] Reduce the number of state updates --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 56b725da0e..01f0e42d92 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -170,7 +170,6 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadComplete(); - StartTimeBindable.BindValueChanged(_ => updateState(State.Value, true)); comboIndexBindable.BindValueChanged(_ => updateComboColour(), true); updateState(ArmedState.Idle, true); @@ -220,6 +219,8 @@ namespace osu.Game.Rulesets.Objects.Drawables } StartTimeBindable.BindTo(HitObject.StartTimeBindable); + StartTimeBindable.BindValueChanged(onStartTimeChanged); + if (HitObject is IHasComboInformation combo) comboIndexBindable.BindTo(combo.ComboIndexBindable); @@ -249,9 +250,11 @@ namespace osu.Game.Rulesets.Objects.Drawables StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) comboIndexBindable.UnbindFrom(combo.ComboIndexBindable); - samplesBindable.UnbindFrom(HitObject.SamplesBindable); + // Changes in start time trigger state updates. When a new hitobject is applied, OnApply() automatically performs a state update anyway. + StartTimeBindable.ValueChanged -= onStartTimeChanged; + // When a new hitobject is applied, the samples will be cleared before re-populating. // In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply(). samplesBindable.CollectionChanged -= onSamplesChanged; @@ -330,6 +333,8 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples(); + private void onStartTimeChanged(ValueChangedEvent startTime) => updateState(State.Value, true); + private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result); private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result); From a07d4a7915aa262bfdfe64e3ce5ba3465a2da523 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 14:42:41 +0900 Subject: [PATCH 28/29] Remove unnecessary dictionary for now --- osu.Game/Rulesets/UI/Playfield.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index d1cb8ecbbd..573a57d701 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -136,8 +136,6 @@ namespace osu.Game.Rulesets.UI return false; } - private readonly Dictionary lifetimeEntryMap = new Dictionary(); - /// /// Adds a for a pooled to this . /// @@ -145,7 +143,6 @@ namespace osu.Game.Rulesets.UI public virtual void Add(HitObjectLifetimeEntry entry) { HitObjectContainer.Add(entry); - lifetimeEntryMap[entry.HitObject] = entry; OnHitObjectAdded(entry.HitObject); } @@ -156,9 +153,8 @@ namespace osu.Game.Rulesets.UI /// Whether the was successfully removed. public virtual bool Remove(HitObjectLifetimeEntry entry) { - if (lifetimeEntryMap.Remove(entry.HitObject)) + if (HitObjectContainer.Remove(entry)) { - HitObjectContainer.Remove(entry); OnHitObjectRemoved(entry.HitObject); return true; } From 4236dd826d7a834c7dc9056bfbfaf2dfa0d99e6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 13 Nov 2020 14:57:09 +0900 Subject: [PATCH 29/29] Improve documentation and make abstract again --- .../Visual/Gameplay/TestScenePoolingRuleset.cs | 2 ++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index c3ae753eae..242eaf7b7d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -142,6 +142,8 @@ namespace osu.Game.Tests.Visual.Gameplay protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject); + public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => null; + protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); protected override Playfield CreatePlayfield() => new TestPlayfield(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0429936d8e..c912348604 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -324,11 +324,16 @@ namespace osu.Game.Rulesets.UI } /// - /// Creates a DrawableHitObject from a HitObject. + /// Creates a to represent a . /// - /// The HitObject to make drawable. - /// The DrawableHitObject. - public virtual DrawableHitObject CreateDrawableRepresentation(TObject h) => null; + /// + /// If this method returns null, then this will assume the requested type is being pooled, + /// and will instead attempt to retrieve the s at the point they should become alive via pools registered through + /// or . + /// + /// The to represent. + /// The representing . + public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter);