From 812a4b412a2c1b13a1e1215a00f863ef6fd83e45 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 19:43:23 +0900 Subject: [PATCH 01/59] Move judgement result revert logic to Playfield Previously, some judgement results were not reverted when the source DHO is not alive (e.g. frames skipped in editor). Now, all results are reverted in the exact reverse order. --- .../TestSceneCatcher.cs | 6 +-- .../UI/CatchComboDisplay.cs | 4 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 4 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 6 +-- .../Judgements/JudgementResultEntry.cs | 26 +++++++++++++ .../Objects/Drawables/DrawableHitObject.cs | 35 ++++++----------- .../Objects/HitObjectLifetimeEntry.cs | 5 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 8 ---- osu.Game/Rulesets/UI/Playfield.cs | 38 ++++++++++++++++--- 11 files changed, 90 insertions(+), 50 deletions(-) create mode 100644 osu.Game/Rulesets/Judgements/JudgementResultEntry.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index e8231b07ad..11e3a5be57 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Catch.Tests }); AddStep("revert second result", () => { - catcher.OnRevertResult(drawableObject2, result2); + catcher.OnRevertResult(result2); }); checkHyperDash(true); AddStep("revert first result", () => { - catcher.OnRevertResult(drawableObject1, result1); + catcher.OnRevertResult(result1); }); checkHyperDash(false); } @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests checkState(CatcherAnimationState.Kiai); AddStep("revert result", () => { - catcher.OnRevertResult(drawableObject, result); + catcher.OnRevertResult(result); }); checkState(CatcherAnimationState.Idle); } diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index dbbe905879..3d0062d32f 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -63,12 +63,12 @@ namespace osu.Game.Rulesets.Catch.UI updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value); } - public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result) + public void OnRevertResult(JudgementResult result) { if (!result.Type.AffectsCombo() || !result.HasResult) return; - updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); + updateCombo(result.ComboAtJudgement, null); } private void updateCombo(int newCombo, Color4? hitObjectColour) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index c33d021876..cf7337fd0d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) => CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result); - private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result) - => CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result); + private void onRevertResult(JudgementResult result) + => CatcherArea.OnRevertResult(result); } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 411330f6fc..1c52c092ec 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -254,7 +254,7 @@ namespace osu.Game.Rulesets.Catch.UI } } - public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result) + public void OnRevertResult(JudgementResult result) { var catchResult = (CatchJudgementResult)result; @@ -268,8 +268,8 @@ namespace osu.Game.Rulesets.Catch.UI SetHyperDashState(); } - caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false); - droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false); + caughtObjectContainer.RemoveAll(d => d.HitObject == result.HitObject, false); + droppedObjectTarget.RemoveAll(d => d.HitObject == result.HitObject, false); } /// diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 4f7535d13a..1b99270b65 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Catch.UI comboDisplay.OnNewResult(hitObject, result); } - public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result) + public void OnRevertResult(JudgementResult result) { - comboDisplay.OnRevertResult(hitObject, result); - Catcher.OnRevertResult(hitObject, result); + comboDisplay.OnRevertResult(result); + Catcher.OnRevertResult(result); } protected override void Update() diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs new file mode 100644 index 0000000000..c3f44804c3 --- /dev/null +++ b/osu.Game/Rulesets/Judgements/JudgementResultEntry.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 System; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Judgements +{ + internal class JudgementResultEntry : IComparable + { + public readonly double Time; + + public readonly HitObjectLifetimeEntry HitObjectEntry; + + public readonly JudgementResult Result; + + public JudgementResultEntry(double time, HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) + { + Time = time; + HitObjectEntry = hitObjectEntry; + Result = result; + } + + public int CompareTo(JudgementResultEntry? other) => Time.CompareTo(other?.Time); + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index be5a7f71e7..02fc5637d8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Invoked by this or a nested prior to a being reverted. /// + /// + /// This is only invoked if this is alive when the result is reverted. + /// public event Action OnRevertResult; /// @@ -222,6 +225,8 @@ namespace osu.Game.Rulesets.Objects.Drawables ensureEntryHasResult(); + entry.RevertResult += onRevertResult; + foreach (var h in HitObject.NestedHitObjects) { var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this); @@ -234,7 +239,6 @@ namespace osu.Game.Rulesets.Objects.Drawables OnNestedDrawableCreated?.Invoke(drawableNested); drawableNested.OnNewResult += onNewResult; - drawableNested.OnRevertResult += onRevertResult; drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState; // This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation(). @@ -309,7 +313,6 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var obj in nestedHitObjects) { obj.OnNewResult -= onNewResult; - obj.OnRevertResult -= onRevertResult; obj.ApplyCustomUpdateState -= onApplyCustomUpdateState; } @@ -318,6 +321,8 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject.DefaultsApplied -= onDefaultsApplied; + entry.RevertResult -= onRevertResult; + OnFree(); ParentHitObject = null; @@ -366,7 +371,11 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result); - private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result); + private void onRevertResult() + { + updateState(ArmedState.Idle); + OnRevertResult?.Invoke(this, Result); + } private void onApplyCustomUpdateState(DrawableHitObject drawableHitObject, ArmedState state) => ApplyCustomUpdateState?.Invoke(drawableHitObject, state); @@ -578,26 +587,6 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - protected override void Update() - { - base.Update(); - - if (Result != null && Result.HasResult) - { - double endTime = HitObject.GetEndTime(); - - if (Result.TimeOffset + endTime > Time.Current) - { - OnRevertResult?.Invoke(this, Result); - - Result.TimeOffset = 0; - Result.Type = HitResult.None; - - updateState(ArmedState.Idle); - } - } - } - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; protected override void UpdateAfterChildren() diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index fedf419973..b517f6b9e6 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.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.Performance; using osu.Game.Rulesets.Judgements; @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Objects private readonly IBindable startTimeBindable = new BindableDouble(); + internal event Action? RevertResult; + /// /// Creates a new . /// @@ -95,5 +98,7 @@ namespace osu.Game.Rulesets.Objects /// Set using . /// internal void SetInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + + internal void OnRevertResult() => RevertResult?.Invoke(); } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 71b452c309..8edc5517cb 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.UI playfield = new Lazy(() => CreatePlayfield().With(p => { p.NewResult += (_, r) => NewResult?.Invoke(r); - p.RevertResult += (_, r) => RevertResult?.Invoke(r); + p.RevertResult += r => RevertResult?.Invoke(r); })); } diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 7cbf49aa31..099be486b3 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -28,11 +28,6 @@ namespace osu.Game.Rulesets.UI /// public event Action NewResult; - /// - /// Invoked when a judgement is reverted. - /// - public event Action RevertResult; - /// /// Invoked when a becomes used by a . /// @@ -111,7 +106,6 @@ namespace osu.Game.Rulesets.UI private void addDrawable(DrawableHitObject drawable) { drawable.OnNewResult += onNewResult; - drawable.OnRevertResult += onRevertResult; bindStartTime(drawable); AddInternal(drawable); @@ -120,7 +114,6 @@ namespace osu.Game.Rulesets.UI private void removeDrawable(DrawableHitObject drawable) { drawable.OnNewResult -= onNewResult; - drawable.OnRevertResult -= onRevertResult; unbindStartTime(drawable); @@ -154,7 +147,6 @@ namespace osu.Game.Rulesets.UI #endregion private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r); - private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r); #region Comparator + StartTime tracking diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index a7881678f1..9535ebb9ed 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -22,6 +22,8 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Objects.Pooling; +using osu.Game.Rulesets.Scoring; +using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Rulesets.UI { @@ -35,9 +37,9 @@ namespace osu.Game.Rulesets.UI public event Action NewResult; /// - /// Invoked when a judgement is reverted. + /// Invoked when a judgement result is reverted. /// - public event Action RevertResult; + public event Action RevertResult; /// /// The contained in this Playfield. @@ -98,6 +100,8 @@ namespace osu.Game.Rulesets.UI private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); + private readonly Stack judgementResults; + /// /// Creates a new . /// @@ -107,14 +111,15 @@ namespace osu.Game.Rulesets.UI hitObjectContainerLazy = new Lazy(() => CreateHitObjectContainer().With(h => { - h.NewResult += (d, r) => NewResult?.Invoke(d, r); - h.RevertResult += (d, r) => RevertResult?.Invoke(d, r); + h.NewResult += onNewResult; h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o); h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o); })); entryManager.OnEntryAdded += onEntryAdded; entryManager.OnEntryRemoved += onEntryRemoved; + + judgementResults = new Stack(); } [BackgroundDependencyLoader] @@ -224,7 +229,7 @@ namespace osu.Game.Rulesets.UI otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements); otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r); - otherPlayfield.RevertResult += (d, r) => RevertResult?.Invoke(d, r); + otherPlayfield.RevertResult += r => RevertResult?.Invoke(r); otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h); otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h); @@ -252,6 +257,10 @@ namespace osu.Game.Rulesets.UI updatable.Update(this); } } + + // When rewinding, revert future judgements in the reverse order. + while (judgementResults.Count > 0 && Time.Current < judgementResults.Peek().Time) + revertResult(judgementResults.Pop()); } /// @@ -443,6 +452,25 @@ namespace osu.Game.Rulesets.UI #endregion + private void onNewResult(DrawableHitObject drawable, JudgementResult result) + { + // Not using result.TimeAbsolute because that might change and also there is a potential precision issue. + judgementResults.Push(new JudgementResultEntry(Time.Current, drawable.Entry.AsNonNull(), result)); + + NewResult?.Invoke(drawable, result); + } + + private void revertResult(JudgementResultEntry entry) + { + var result = entry.Result; + RevertResult?.Invoke(result); + + result.TimeOffset = 0; + result.Type = HitResult.None; + + entry.HitObjectEntry.OnRevertResult(); + } + #region Editor logic /// From 32acaa44be3009b3120e05753308e157c8f0219a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 19:52:37 +0900 Subject: [PATCH 02/59] Remove now-redundant code --- .../TestSceneCatcher.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 11e3a5be57..f60ae29f77 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -60,17 +60,15 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestCatcherHyperStateReverted() { - DrawableCatchHitObject drawableObject1 = null; - DrawableCatchHitObject drawableObject2 = null; JudgementResult result1 = null; JudgementResult result2 = null; AddStep("catch hyper fruit", () => { - attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1); + result1 = attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }); }); AddStep("catch normal fruit", () => { - attemptCatch(new Fruit(), out drawableObject2, out result2); + result2 = attemptCatch(new Fruit()); }); AddStep("revert second result", () => { @@ -87,11 +85,10 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestCatcherAnimationStateReverted() { - DrawableCatchHitObject drawableObject = null; JudgementResult result = null; AddStep("catch kiai fruit", () => { - attemptCatch(new TestKiaiFruit(), out drawableObject, out result); + result = attemptCatch(new TestKiaiFruit()); }); checkState(CatcherAnimationState.Kiai); AddStep("revert result", () => @@ -268,23 +265,19 @@ namespace osu.Game.Rulesets.Catch.Tests private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state); - private void attemptCatch(CatchHitObject hitObject) - { - attemptCatch(() => hitObject, 1); - } - private void attemptCatch(Func hitObject, int count) { for (int i = 0; i < count; i++) - attemptCatch(hitObject(), out _, out _); + attemptCatch(hitObject()); } - private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result) + private JudgementResult attemptCatch(CatchHitObject hitObject) { hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableObject = createDrawableObject(hitObject); - result = createResult(hitObject); + var drawableObject = createDrawableObject(hitObject); + var result = createResult(hitObject); applyResult(drawableObject, result); + return result; } private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result) From 11e1b22bf5be695bebc5dad23ea9c2aa268d4be7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 20:53:35 +0900 Subject: [PATCH 03/59] Move MaximumJudgementOffset to HitObject We want to access this property for computing lifetime --- .../Objects/Drawables/DrawableHoldNote.cs | 2 -- .../Objects/Drawables/DrawableHoldNoteTail.cs | 11 +---------- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 ++ osu.Game.Rulesets.Mania/Objects/TailNote.cs | 9 +++++++++ .../Objects/Drawables/DrawableSpinnerTick.cs | 2 -- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 7 +++++++ .../Objects/Drawables/DrawableDrumRollTick.cs | 2 -- osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs | 2 ++ .../Objects/Drawables/DrawableHitObject.cs | 14 +------------- osu.Game/Rulesets/Objects/HitObject.cs | 8 ++++++++ 11 files changed, 32 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 759d2346e8..8863de5ee3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private double? releaseTime; - public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset; - public DrawableHoldNote() : this(null) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index bf477277c6..20ea962994 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -15,13 +15,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public partial class DrawableHoldNoteTail : DrawableNote { - /// - /// Lenience of release hit windows. This is to make cases where the hold note release - /// is timed alongside presses of other hit objects less awkward. - /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps - /// - private const double release_window_lenience = 1.5; - protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail; protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject; @@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); - public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience; - protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); // Factor in the release lenience - timeOffset /= release_window_lenience; + timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE; if (!userTriggered) { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 22fab15c1b..c367886efe 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects /// public TailNote Tail { get; private set; } + public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset; + /// /// The time between ticks of this hold. /// diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index cda8e2fa31..d6dc25079a 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs @@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects { public class TailNote : Note { + /// + /// Lenience of release hit windows. This is to make cases where the hold note release + /// is timed alongside presses of other hit objects less awkward. + /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps + /// + public const double RELEASE_WINDOW_LENIENCE = 1.5; + public override Judgement CreateJudgement() => new ManiaJudgement(); + + public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index b9ce07363c..34253e3d4f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; } - public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; - /// /// Apply a judgement result. /// diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0e1fe56cea..ed6f8a9a6a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired - ? new SpinnerTick { StartTime = startTime } - : new SpinnerBonusTick { StartTime = startTime }); + ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration } + : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 650d02c675..c890f3771b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerTick : OsuHitObject { + /// + /// Duration of the containing this spinner tick. + /// + public double SpinnerDuration { get; set; } + public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + public override double MaximumJudgementOffset => SpinnerDuration; + public class OsuSpinnerTickJudgement : OsuJudgement { public override HitResult MaxResult => HitResult.SmallBonus; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 45e25ee7dc..abecd19545 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece()); - public override double MaximumJudgementOffset => HitObject.HitWindow; - protected override void OnApply() { base.OnApply(); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 433fdab908..6bcb8674e6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; + public override double MaximumJudgementOffset => HitWindow; + protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime }; public class StrongNestedHit : StrongNestedHitObject diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index be5a7f71e7..43431d7703 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -651,18 +651,6 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } - /// - /// The maximum offset from the end time of at which this can be judged. - /// The time offset of will be clamped to this value during . - /// - /// Defaults to the miss window of . - /// - /// - /// - /// This does not affect the time offset provided to invocations of . - /// - public virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0; - /// /// Applies the of this , notifying responders such as /// the of the . @@ -684,7 +672,7 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); + Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 0f79e58201..25f538d211 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -200,6 +200,14 @@ namespace osu.Game.Rulesets.Objects [NotNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); + /// + /// The maximum offset from the end time of at which this can be judged. + /// + /// Defaults to the miss window. + /// + /// + public virtual double MaximumJudgementOffset => HitWindows?.WindowFor(HitResult.Miss) ?? 0; + public IList CreateSlidingSamples() { var slidingSamples = new List(); From d8f9b7d02f2a4e3b86976b45226f7488ef1639f8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 21:25:21 +0900 Subject: [PATCH 04/59] Use MaximumJudgementOffset for lifetime --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 3559a1521c..b93a427196 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -232,8 +232,7 @@ namespace osu.Game.Rulesets.UI.Scrolling double computedStartTime = computeDisplayStartTime(entry); // always load the hitobject before its first judgement offset - double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0; - entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime); + entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - entry.HitObject.MaximumJudgementOffset, computedStartTime); } private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null) From 75a1a2ec2f2d136a510f05c2371b6d37fc8270a9 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 22 Jan 2023 03:44:59 +0100 Subject: [PATCH 05/59] Hide `ResumeOverlay` when `OsuModAutopilot` is enabled --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 1 + osu.Game/Rulesets/Mods/Mod.cs | 3 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 6772cfe0be..99a35f4d69 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 0.1; + public override bool HidesResumeOverlay => true; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 04d55bc5fe..4f85bf4663 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -107,6 +107,9 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; + [JsonIgnore] + public virtual bool HidesResumeOverlay => false; + /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 71b452c309..38dbb1308f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -230,7 +230,9 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + if (ResumeOverlay != null + && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + && Mods.All(mod => !mod.HidesResumeOverlay)) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; From 8405a3e1722f0b3b3d11d2ff33a08fb1dfdafb56 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 23 Jan 2023 18:51:55 +0900 Subject: [PATCH 06/59] Add test for RevertResult --- .../Gameplay/TestScenePoolingRuleset.cs | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 55ee6c9fc9..22ab74cc30 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -19,7 +19,6 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -37,6 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay private TestDrawablePoolingRuleset drawableRuleset; + private TestPlayfield playfield => (TestPlayfield)drawableRuleset.Playfield; + [Test] public void TestReusedWithHitObjectsSpacedFarApart() { @@ -133,29 +134,49 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("no DHOs shown", () => !this.ChildrenOfType().Any()); } + [Test] + public void TestRevertResult() + { + ManualClock clock = null; + Beatmap beatmap; + + createTest(beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject { StartTime = 0 }, + new TestHitObject { StartTime = 500 }, + new TestHitObject { StartTime = 1000 }, + } + }, 10, () => new FramedClock(clock = new ManualClock())); + + AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + + AddStep("rewind to middle", () => clock.CurrentTime = beatmap.HitObjects[1].StartTime - 100); + AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count == 1); + + AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + + AddStep("disable frame stability", () => drawableRuleset.FrameStablePlayback = false); + AddStep("instant seek to start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime - 100); + AddAssert("all results reverted", () => playfield.JudgedObjects.Count == 0); + } + [Test] public void TestApplyHitResultOnKilled() { ManualClock clock = null; - bool anyJudged = false; - - void onNewResult(JudgementResult _) => anyJudged = true; var beatmap = new Beatmap(); beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 }); createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock())); - AddStep("subscribe to new result", () => - { - anyJudged = false; - drawableRuleset.NewResult += onNewResult; - }); AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000); - AddAssert("object judged", () => anyJudged); - - AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult); + AddAssert("object judged", () => playfield.JudgedObjects.Count == 1); } private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null) @@ -212,12 +233,24 @@ namespace osu.Game.Tests.Visual.Gameplay private partial class TestPlayfield : Playfield { + public readonly HashSet JudgedObjects = new HashSet(); + private readonly int poolSize; public TestPlayfield(int poolSize) { this.poolSize = poolSize; AddInternal(HitObjectContainer); + NewResult += (_, r) => + { + Assert.That(JudgedObjects, Has.No.Member(r.HitObject)); + JudgedObjects.Add(r.HitObject); + }; + RevertResult += r => + { + Assert.That(JudgedObjects, Has.Member(r.HitObject)); + JudgedObjects.Remove(r.HitObject); + }; } [BackgroundDependencyLoader] From 8b47af6503b5a93e98940d31b96d13b1858361f5 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 24 Jan 2023 00:49:09 +0100 Subject: [PATCH 07/59] Remove `HidesResumeOverlay` and set `ResumeOverlay` to `null` in `OsuModAutopilot` --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 ++- osu.Game/Rulesets/Mods/Mod.cs | 3 --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 99a35f4d69..276724c169 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 0.1; - public override bool HidesResumeOverlay => true; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; @@ -55,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + drawableRuleset.ResumeOverlay = null; + // Grab the input manager to disable the user's cursor, and for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; inputManager.AllowUserCursorMovement = false; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 4f85bf4663..04d55bc5fe 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -107,9 +107,6 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; - [JsonIgnore] - public virtual bool HidesResumeOverlay => false; - /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 38dbb1308f..403dee5651 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -230,9 +230,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null - && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) - && Mods.All(mod => !mod.HidesResumeOverlay)) + if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; @@ -507,7 +505,7 @@ namespace osu.Game.Rulesets.UI /// /// An optional overlay used when resuming gameplay from a paused state. /// - public ResumeOverlay ResumeOverlay { get; protected set; } + public ResumeOverlay ResumeOverlay { get; set; } /// /// Returns first available provided by a . From e66e43e17ce3330f5924738a28de44f6593c2bc2 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 14:15:17 +0900 Subject: [PATCH 08/59] Remove unused code --- osu.Game/Rulesets/Judgements/JudgementResultEntry.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs index c3f44804c3..b9d75d3acb 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Judgements { - internal class JudgementResultEntry : IComparable + internal class JudgementResultEntry { public readonly double Time; @@ -20,7 +19,5 @@ namespace osu.Game.Rulesets.Judgements HitObjectEntry = hitObjectEntry; Result = result; } - - public int CompareTo(JudgementResultEntry? other) => Time.CompareTo(other?.Time); } } \ No newline at end of file From cc87923179ccaa9533be0a2501e31f95e6aba6c8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 14:19:24 +0900 Subject: [PATCH 09/59] Fix OnRevertResult timing --- osu.Game/Rulesets/UI/Playfield.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 9535ebb9ed..1d9390ea14 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -464,11 +464,10 @@ namespace osu.Game.Rulesets.UI { var result = entry.Result; RevertResult?.Invoke(result); + entry.HitObjectEntry.OnRevertResult(); result.TimeOffset = 0; result.Type = HitResult.None; - - entry.HitObjectEntry.OnRevertResult(); } #region Editor logic From efef97d5be5f9bcf38c67713c572f3e48c0c5625 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 15:35:06 +0900 Subject: [PATCH 10/59] Store Result.TimeAbsolute separately from offset Calculating from TimeOffset is bad because it loses precision. The result time won't change anymore even If `HitObject.GetEndTime()` changes later. --- osu.Game/Rulesets/Judgements/JudgementResult.cs | 17 ++++++++++++++--- .../Rulesets/Judgements/JudgementResultEntry.cs | 5 ++--- .../Objects/Drawables/DrawableHitObject.cs | 3 ++- osu.Game/Rulesets/UI/Playfield.cs | 5 ++--- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 2685cb4e2a..9fdbdae396 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -33,16 +33,19 @@ namespace osu.Game.Rulesets.Judgements public readonly Judgement Judgement; /// - /// The offset from a perfect hit at which this occurred. + /// The offset of from the end time of , clamped by . /// Populated when this is applied via . /// public double TimeOffset { get; internal set; } /// /// The absolute time at which this occurred. - /// Equal to the (end) time of the + . + /// Populated when this is applied via . /// - public double TimeAbsolute => HitObject.GetEndTime() + TimeOffset; + /// + /// This is initially set to the end time of . + /// + public double TimeAbsolute { get; internal set; } /// /// The combo prior to this occurring. @@ -83,6 +86,14 @@ namespace osu.Game.Rulesets.Judgements { HitObject = hitObject; Judgement = judgement; + Reset(); + } + + internal void Reset() + { + Type = HitResult.None; + TimeOffset = 0; + TimeAbsolute = HitObject.GetEndTime(); } public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs index b9d75d3acb..09351e6974 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs @@ -7,15 +7,14 @@ namespace osu.Game.Rulesets.Judgements { internal class JudgementResultEntry { - public readonly double Time; + public double Time => Result.TimeAbsolute; public readonly HitObjectLifetimeEntry HitObjectEntry; public readonly JudgementResult Result; - public JudgementResultEntry(double time, HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) + public JudgementResultEntry(HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) { - Time = time; HitObjectEntry = hitObjectEntry; Result = result; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 02fc5637d8..0c59d638b6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -673,7 +673,8 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); + Result.TimeAbsolute = Time.Current; + Result.TimeOffset = Math.Min(MaximumJudgementOffset, Result.TimeAbsolute - HitObject.GetEndTime()); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 1d9390ea14..40e84a60e3 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -455,7 +455,7 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { // Not using result.TimeAbsolute because that might change and also there is a potential precision issue. - judgementResults.Push(new JudgementResultEntry(Time.Current, drawable.Entry.AsNonNull(), result)); + judgementResults.Push(new JudgementResultEntry(drawable.Entry.AsNonNull(), result)); NewResult?.Invoke(drawable, result); } @@ -466,8 +466,7 @@ namespace osu.Game.Rulesets.UI RevertResult?.Invoke(result); entry.HitObjectEntry.OnRevertResult(); - result.TimeOffset = 0; - result.Type = HitResult.None; + result.Reset(); } #region Editor logic From e1702a8ee9d50cf15f1a2cd02a64d03a245a0d3f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 24 Jan 2023 15:43:57 +0900 Subject: [PATCH 11/59] Fix inspection issue --- osu.Game/Rulesets/UI/Playfield.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 40e84a60e3..3ec31db4b9 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Objects.Pooling; -using osu.Game.Rulesets.Scoring; using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Rulesets.UI From e3a5c233e9b8510e6e199eeabf39de0ad8a5145b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 17:39:35 +0900 Subject: [PATCH 12/59] Update test to use newer assetion logic --- osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 22ab74cc30..0469df1de3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -151,17 +151,17 @@ namespace osu.Game.Tests.Visual.Gameplay }, 10, () => new FramedClock(clock = new ManualClock())); AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); - AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3)); AddStep("rewind to middle", () => clock.CurrentTime = beatmap.HitObjects[1].StartTime - 100); - AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count == 1); + AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(1)); AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100); - AddUntilStep("all judged", () => playfield.JudgedObjects.Count == 3); + AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3)); AddStep("disable frame stability", () => drawableRuleset.FrameStablePlayback = false); AddStep("instant seek to start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime - 100); - AddAssert("all results reverted", () => playfield.JudgedObjects.Count == 0); + AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0)); } [Test] From 27578c48f5fd43a42cde43acb290c10dac9c3fe4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 27 Jan 2023 19:35:44 +0900 Subject: [PATCH 13/59] Remove JudgementResultEntry It is not needed anymore as TimeAbsolute is stored raw. --- .../Judgements/JudgementResultEntry.cs | 22 ------------------- osu.Game/Rulesets/UI/Playfield.cs | 18 ++++++++------- 2 files changed, 10 insertions(+), 30 deletions(-) delete mode 100644 osu.Game/Rulesets/Judgements/JudgementResultEntry.cs diff --git a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs b/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs deleted file mode 100644 index 09351e6974..0000000000 --- a/osu.Game/Rulesets/Judgements/JudgementResultEntry.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Judgements -{ - internal class JudgementResultEntry - { - public double Time => Result.TimeAbsolute; - - public readonly HitObjectLifetimeEntry HitObjectEntry; - - public readonly JudgementResult Result; - - public JudgementResultEntry(HitObjectLifetimeEntry hitObjectEntry, JudgementResult result) - { - HitObjectEntry = hitObjectEntry; - Result = result; - } - } -} \ No newline at end of file diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 3ec31db4b9..5d6a8de33c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.UI private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); - private readonly Stack judgementResults; + private readonly Stack judgedEntries; /// /// Creates a new . @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.UI entryManager.OnEntryAdded += onEntryAdded; entryManager.OnEntryRemoved += onEntryRemoved; - judgementResults = new Stack(); + judgedEntries = new Stack(); } [BackgroundDependencyLoader] @@ -258,8 +258,8 @@ namespace osu.Game.Rulesets.UI } // When rewinding, revert future judgements in the reverse order. - while (judgementResults.Count > 0 && Time.Current < judgementResults.Peek().Time) - revertResult(judgementResults.Pop()); + while (judgedEntries.Count > 0 && Time.Current < judgedEntries.Peek().Result.AsNonNull().TimeAbsolute) + revertResult(judgedEntries.Pop()); } /// @@ -453,17 +453,19 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { - // Not using result.TimeAbsolute because that might change and also there is a potential precision issue. - judgementResults.Push(new JudgementResultEntry(drawable.Entry.AsNonNull(), result)); + Debug.Assert(result != null && drawable.Entry?.Result == result); + judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); } - private void revertResult(JudgementResultEntry entry) + private void revertResult(HitObjectLifetimeEntry entry) { var result = entry.Result; + Debug.Assert(result != null); + RevertResult?.Invoke(result); - entry.HitObjectEntry.OnRevertResult(); + entry.OnRevertResult(); result.Reset(); } From 258de3b2d89cc2a7a4a624202c8460be89e83638 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Feb 2023 17:15:37 +0900 Subject: [PATCH 14/59] Store RawTime in JudgementResult --- .../Rulesets/Judgements/JudgementResult.cs | 33 ++++++++++++------- .../Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Rulesets/UI/Playfield.cs | 12 +++++-- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 6749fd7932..bf29919e34 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using JetBrains.Annotations; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -33,19 +34,30 @@ namespace osu.Game.Rulesets.Judgements public readonly Judgement Judgement; /// - /// The offset of from the end time of , clamped by . - /// Populated when this is applied via . - /// - public double TimeOffset { get; internal set; } - - /// - /// The absolute time at which this occurred. + /// The time at which this occurred. /// Populated when this is applied via . /// /// - /// This is initially set to the end time of . + /// This is used instead of to check whether this should be reverted. /// - public double TimeAbsolute { get; internal set; } + internal double? RawTime { get; set; } + + /// + /// The offset of from the end time of , clamped by . + /// + public double TimeOffset + { + get => RawTime != null ? Math.Min(RawTime.Value - HitObject.GetEndTime(), HitObject.MaximumJudgementOffset) : 0; + internal set => RawTime = HitObject.GetEndTime() + value; + } + + /// + /// The absolute time at which this occurred, clamped by the end time of plus . + /// + /// + /// The end time of is returned if this result is not populated yet. + /// + public double TimeAbsolute => RawTime != null ? Math.Min(RawTime.Value, HitObject.GetEndTime() + HitObject.MaximumJudgementOffset) : HitObject.GetEndTime(); /// /// The combo prior to this occurring. @@ -92,8 +104,7 @@ namespace osu.Game.Rulesets.Judgements internal void Reset() { Type = HitResult.None; - TimeOffset = 0; - TimeAbsolute = HitObject.GetEndTime(); + RawTime = null; } public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d30fc00bfc..f7c6340419 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -661,7 +661,7 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); + Result.RawTime = Time.Current; if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 5d6a8de33c..b1c3b78e67 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -258,8 +258,16 @@ namespace osu.Game.Rulesets.UI } // When rewinding, revert future judgements in the reverse order. - while (judgedEntries.Count > 0 && Time.Current < judgedEntries.Peek().Result.AsNonNull().TimeAbsolute) + while (judgedEntries.Count > 0) + { + var result = judgedEntries.Peek().Result; + Debug.Assert(result?.RawTime != null); + + if (Time.Current >= result.RawTime.Value) + break; + revertResult(judgedEntries.Pop()); + } } /// @@ -453,7 +461,7 @@ namespace osu.Game.Rulesets.UI private void onNewResult(DrawableHitObject drawable, JudgementResult result) { - Debug.Assert(result != null && drawable.Entry?.Result == result); + Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null); judgedEntries.Push(drawable.Entry.AsNonNull()); NewResult?.Invoke(drawable, result); From 5ddaf8ea3c2d0f79f891a1a8672e534e3e2dc13e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 9 Feb 2023 17:43:04 +0900 Subject: [PATCH 15/59] Fix test setting invalid TimeOffset --- .../Visual/Gameplay/TestSceneUnstableRateCounter.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index 2f572b46c9..d0e516ed39 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -22,12 +22,18 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(ScoreProcessor))] private TestScoreProcessor scoreProcessor = new TestScoreProcessor(); - private readonly OsuHitWindows hitWindows = new OsuHitWindows(); + private readonly OsuHitWindows hitWindows; private UnstableRateCounter counter; private double prev; + public TestSceneUnstableRateCounter() + { + hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(5); + } + [SetUpSteps] public void SetUp() { From e4b84ebd0b3a89e56c07da34195862bccfbbb38c Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 13 Feb 2023 23:51:39 +0100 Subject: [PATCH 16/59] Add `UseResumeOverlay` and use it for hiding the `ResumeOverlay` --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 276724c169..9eb0a46bfb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -54,14 +54,14 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.ResumeOverlay = null; - // Grab the input manager to disable the user's cursor, and for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; inputManager.AllowUserCursorMovement = false; // Generate the replay frames the cursor should follow replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast().ToList(); + + drawableRuleset.UseResumeOverlay = false; } } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 403dee5651..23e8d1fc2d 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -196,6 +196,8 @@ namespace osu.Game.Rulesets.UI if ((ResumeOverlay = CreateResumeOverlay()) != null) { + UseResumeOverlay = true; + AddInternal(CreateInputManager() .WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(ResumeOverlay))); @@ -230,7 +232,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + if (ResumeOverlay != null && UseResumeOverlay && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; @@ -507,6 +509,12 @@ namespace osu.Game.Rulesets.UI /// public ResumeOverlay ResumeOverlay { get; set; } + /// + /// Whether the should be used to return the user's cursor position to its previous location after a pause. + /// + /// Defaults to true if a ruleset returns a non-null overlay via . + public bool UseResumeOverlay { get; set; } + /// /// Returns first available provided by a . /// From a820c0c8ebdc7d1e6ccb5e6a59d0cb5c50851d51 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 13 Feb 2023 23:55:13 +0100 Subject: [PATCH 17/59] Add `TestSceneInstantResume` --- .../TestSceneInstantResume.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs new file mode 100644 index 0000000000..2bc6386185 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs @@ -0,0 +1,34 @@ +// 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.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneInstantResume : TestSceneOsuPlayer + { + protected override bool HasCustomSteps => true; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = new[] { new OsuModAutopilot() }; + return new TestPlayer(); + } + + [Test] + public void TestInstantResume() + { + CreateTest(); + + AddStep("press pause", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value); + AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape)); + AddStep("press resume", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait for resume", () => !Player.IsResuming); + AddAssert("resumed", () => !Player.GameplayClockContainer.IsPaused.Value); + } + } +} From 9e04a36d86c9a19e37cf470aa4c4505fa9ad4ef9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Feb 2023 15:07:45 +0900 Subject: [PATCH 18/59] Move test to a mod test and add more resilient test logic --- .../TestSceneOsuModAutopilot.cs} | 21 ++++++++----------- osu.Game/Tests/Visual/ModTestScene.cs | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneInstantResume.cs => Mods/TestSceneOsuModAutopilot.cs} (66%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs similarity index 66% rename from osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs rename to osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs index 2bc6386185..37b31d1d1a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs @@ -3,26 +3,23 @@ using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Tests.Visual; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Mods { - public partial class TestSceneInstantResume : TestSceneOsuPlayer + public partial class TestSceneOsuModAutopilot : OsuModTestScene { - protected override bool HasCustomSteps => true; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) - { - SelectedMods.Value = new[] { new OsuModAutopilot() }; - return new TestPlayer(); - } - [Test] public void TestInstantResume() { - CreateTest(); + CreateModTest(new ModTestData + { + Mod = new OsuModAutopilot(), + PassCondition = () => true, + Autoplay = false, + }); + AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value); AddStep("press pause", () => InputManager.PressKey(Key.Escape)); AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value); AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape)); diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 0559d80384..aa5b506343 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual protected override bool CheckModsAllowFailure() => allowFail; public ModTestPlayer(ModTestData data, bool allowFail) - : base(false, false) + : base(true, false) { this.allowFail = allowFail; currentTestData = data; From 63f349876290a3d5c07768e75116ae132dbbc2ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Feb 2023 15:11:33 +0900 Subject: [PATCH 19/59] Restructure `UseResumeOverlay` to correctly handle a value change before BDL load --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 23e8d1fc2d..c839062875 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -194,13 +194,16 @@ namespace osu.Game.Rulesets.UI Audio = audioContainer; - if ((ResumeOverlay = CreateResumeOverlay()) != null) + if (UseResumeOverlay) { - UseResumeOverlay = true; - - AddInternal(CreateInputManager() - .WithChild(CreatePlayfieldAdjustmentContainer() - .WithChild(ResumeOverlay))); + if ((ResumeOverlay = CreateResumeOverlay()) == null) + UseResumeOverlay = false; + else + { + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); + } } applyRulesetMods(Mods, config); @@ -512,8 +515,8 @@ namespace osu.Game.Rulesets.UI /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. /// - /// Defaults to true if a ruleset returns a non-null overlay via . - public bool UseResumeOverlay { get; set; } + /// Defaults to true, but will become false if the ruleset fails to return a non-null overlay via . + public bool UseResumeOverlay { get; set; } = true; /// /// Returns first available provided by a . From 19e3c5d33c5495cc17180932806db5e3a1c34c14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 13:59:24 +0900 Subject: [PATCH 20/59] Adjust song select background dimming to be more evenly applied --- osu.Game/Screens/Select/SongSelect.cs | 2 ++ osu.Game/Screens/Select/WedgeBackground.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8786821c77..5c4a42852c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -767,6 +767,8 @@ namespace osu.Game.Screens.Select { backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.4f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index da12c1a67a..fd4b91bf5f 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Size = new Vector2(1, 0.5f), - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.2f), Shear = new Vector2(0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select RelativePositionAxes = Axes.Y, Size = new Vector2(1, -0.5f), Position = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.2f), Shear = new Vector2(-0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, From 9ed068c1e67e870f49163620cf95697c5426a108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 14:16:34 +0900 Subject: [PATCH 21/59] Only apply dim changes when background blur is disabled --- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 16 +++++++++++++--- osu.Game/Screens/Select/WedgeBackground.cs | 9 +++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 25830c9d54..6f6292c3b2 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers /// public const float BREAK_LIGHTEN_AMOUNT = 0.3f; - protected const double BACKGROUND_FADE_DURATION = 800; + public const double BACKGROUND_FADE_DURATION = 800; /// /// Whether or not user-configured settings relating to brightness of elements should be ignored diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5c4a42852c..8546dfeeaa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -31,6 +31,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -148,7 +149,7 @@ namespace osu.Game.Screens.Select if (!this.IsCurrentScreen()) return; - ApplyToBackground(b => b.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0); + ApplyToBackground(applyBlurToBackground); }); LoadComponentAsync(Carousel = new BeatmapCarousel @@ -194,6 +195,7 @@ namespace osu.Game.Screens.Select { ParallaxAmount = 0.005f, RelativeSizeAxes = Axes.Both, + Alpha = 0, Anchor = Anchor.Centre, Origin = Anchor.Centre, Child = new WedgeBackground @@ -766,10 +768,10 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; backgroundModeBeatmap.IgnoreUserSettings.Value = true; - backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.4f; backgroundModeBeatmap.FadeColour(Color4.White, 250); + + applyBlurToBackground(backgroundModeBeatmap); }); beatmapInfoWedge.Beatmap = beatmap; @@ -787,6 +789,14 @@ namespace osu.Game.Screens.Select } } + private void applyBlurToBackground(BackgroundScreenBeatmap backgroundModeBeatmap) + { + backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = configBackgroundBlur.Value ? 0 : 0.4f; + + wedgeBackground.FadeTo(configBackgroundBlur.Value ? 0.5f : 0.2f, UserDimContainer.BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + private readonly WeakReference lastTrack = new WeakReference(null); /// diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index fd4b91bf5f..2e2b43cd70 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osuTK; using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Select { @@ -22,7 +19,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Size = new Vector2(1, 0.5f), - Colour = Color4.Black.Opacity(0.2f), + Colour = Color4.Black, Shear = new Vector2(0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, @@ -32,7 +29,7 @@ namespace osu.Game.Screens.Select RelativePositionAxes = Axes.Y, Size = new Vector2(1, -0.5f), Position = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.2f), + Colour = Color4.Black, Shear = new Vector2(-0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, From ce9ef3bc3c0092b99c904ba89c0a7ce99be6cab4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 15:47:20 +0900 Subject: [PATCH 22/59] Always create `ResumeOverlay`, with `UseResumeOverlay` flag only affecting whether it is displayed or not --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index c839062875..d57aec1271 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -194,16 +194,11 @@ namespace osu.Game.Rulesets.UI Audio = audioContainer; - if (UseResumeOverlay) + if ((ResumeOverlay = CreateResumeOverlay()) != null) { - if ((ResumeOverlay = CreateResumeOverlay()) == null) - UseResumeOverlay = false; - else - { - AddInternal(CreateInputManager() - .WithChild(CreatePlayfieldAdjustmentContainer() - .WithChild(ResumeOverlay))); - } + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); } applyRulesetMods(Mods, config); @@ -515,7 +510,10 @@ namespace osu.Game.Rulesets.UI /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. /// - /// Defaults to true, but will become false if the ruleset fails to return a non-null overlay via . + /// + /// Defaults to true. + /// Even if true, will not have any effect if the ruleset does not have a resume overlay (see ). + /// public bool UseResumeOverlay { get; set; } = true; /// @@ -542,6 +540,11 @@ namespace osu.Game.Rulesets.UI } } + /// + /// Create an optional resume overlay, which is displayed when a player requests to resume gameplay during non-break time. + /// This can be used to force the player to return their hands / cursor to the position they left off, to avoid players + /// using pauses as a means of adjusting their inputs (aka "pause buffering"). + /// protected virtual ResumeOverlay CreateResumeOverlay() => null; /// From 9d02a2ef0e8a17398459403ac71353d4688ad4ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 17:37:46 +0900 Subject: [PATCH 23/59] Apply NRT to `GamepleSampleTriggerSource` tests --- .../Gameplay/TestSceneGameplaySampleTriggerSource.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index e184d50d7c..a9b469fd86 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Audio; @@ -20,10 +18,10 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene { - private TestGameplaySampleTriggerSource sampleTriggerSource; + private TestGameplaySampleTriggerSource sampleTriggerSource = null!; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - private Beatmap beatmap; + private Beatmap beatmap = null!; protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { @@ -80,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCorrectHitObject() { - HitObjectLifetimeEntry nextObjectEntry = null; + HitObjectLifetimeEntry? nextObjectEntry = null; AddAssert("no alive objects", () => getNextAliveObject() == null); @@ -103,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("hit first hitobject", () => { InputManager.Click(MouseButton.Left); - return nextObjectEntry.Result?.HasResult == true; + return nextObjectEntry?.Result?.HasResult == true; }); AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); @@ -115,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); } - private DrawableHitObject getNextAliveObject() => + private DrawableHitObject? getNextAliveObject() => Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); [Test] From 979c079f8b5f852575e9722303173d28facbd31a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 17:58:32 +0900 Subject: [PATCH 24/59] Refactor `GameplaySampleTriggerSource` test to not be realtime dependent --- .../TestSceneGameplaySampleTriggerSource.cs | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index a9b469fd86..e7bdf7b9ba 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -1,8 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -12,6 +16,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Storyboards; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -23,6 +28,12 @@ namespace osu.Game.Tests.Visual.Gameplay private Beatmap beatmap = null!; + [Resolved] + private AudioManager audio { get; set; } = null!; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { beatmap = new Beatmap @@ -37,12 +48,13 @@ namespace osu.Game.Tests.Visual.Gameplay const double start_offset = 8000; const double spacing = 2000; + // intentionally start objects a bit late so we can test the case of no alive objects. double t = start_offset; + beatmap.HitObjects.AddRange(new[] { new HitCircle { - // intentionally start objects a bit late so we can test the case of no alive objects. StartTime = t += spacing, Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } }, @@ -78,41 +90,64 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCorrectHitObject() { - HitObjectLifetimeEntry? nextObjectEntry = null; + waitForAliveObjectIndex(null); - AddAssert("no alive objects", () => getNextAliveObject() == null); + seekBeforeIndex(0); + waitForAliveObjectIndex(0); + checkValidObjectIndex(0); - AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]); + AddAssert("first object not hit", () => getNextAliveObject()?.Entry?.Result?.HasResult != true); - AddUntilStep("get next object", () => + AddStep("hit first object", () => { - var nextDrawableObject = getNextAliveObject(); + var next = getNextAliveObject(); - if (nextDrawableObject != null) + if (next != null) { - nextObjectEntry = nextDrawableObject.Entry; - InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre); - return true; + Debug.Assert(next.Entry?.Result?.HasResult != true); + + InputManager.MoveMouseTo(next.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); } - - return false; }); - AddUntilStep("hit first hitobject", () => - { - InputManager.Click(MouseButton.Left); - return nextObjectEntry?.Result?.HasResult == true; - }); + AddAssert("first object hit", () => getNextAliveObject()?.Entry?.Result?.HasResult == true); - AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); + checkValidObjectIndex(1); - AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]); - AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + // Still object 1 as it's not hit yet. + seekBeforeIndex(1); + waitForAliveObjectIndex(1); + checkValidObjectIndex(1); - AddUntilStep("no alive objects", () => getNextAliveObject() == null); - AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + seekBeforeIndex(2); + waitForAliveObjectIndex(2); + checkValidObjectIndex(2); + + seekBeforeIndex(3); + waitForAliveObjectIndex(3); + checkValidObjectIndex(3); + + AddStep("Seek into future", () => Beatmap.Value.Track.Seek(beatmap.HitObjects.Last().GetEndTime() + 10000)); + + waitForAliveObjectIndex(null); + checkValidObjectIndex(3); } + private void seekBeforeIndex(int index) => + AddStep($"seek to just before object {index}", () => Beatmap.Value.Track.Seek(beatmap.HitObjects[index].StartTime - 100)); + + private void waitForAliveObjectIndex(int? index) + { + if (index == null) + AddUntilStep("wait for no alive objects", getNextAliveObject, () => Is.Null); + else + AddUntilStep($"wait for next alive to be {index}", () => getNextAliveObject()?.HitObject, () => Is.EqualTo(beatmap.HitObjects[index.Value])); + } + + private void checkValidObjectIndex(int index) => + AddAssert($"check valid object is {index}", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[index])); + private DrawableHitObject? getNextAliveObject() => Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); From 394d368f165431e7bb58bc4d365ee7f7fbb6614b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 18:45:22 +0900 Subject: [PATCH 25/59] Fix song select potentially updating background parameters when not the current screen --- .../Screens/Play/ScreenWithBeatmapBackground.cs | 11 ++++++++--- osu.Game/Screens/Select/SongSelect.cs | 16 ++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index d6c8a0ad6a..a5d1961bd8 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Diagnostics; +using osu.Framework.Screens; using osu.Game.Screens.Backgrounds; namespace osu.Game.Screens.Play @@ -12,6 +12,11 @@ namespace osu.Game.Screens.Play { protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); - public void ApplyToBackground(Action action) => base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b)); + public void ApplyToBackground(Action action) + { + Debug.Assert(this.IsCurrentScreen()); + + base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b)); + } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8546dfeeaa..8a9a20261e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -765,14 +765,18 @@ namespace osu.Game.Screens.Select /// The working beatmap. private void updateComponentFromBeatmap(WorkingBeatmap beatmap) { - ApplyToBackground(backgroundModeBeatmap => + // If not the current screen, this will be applied in OnResuming. + if (this.IsCurrentScreen()) { - backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.IgnoreUserSettings.Value = true; - backgroundModeBeatmap.FadeColour(Color4.White, 250); + ApplyToBackground(backgroundModeBeatmap => + { + backgroundModeBeatmap.Beatmap = beatmap; + backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.FadeColour(Color4.White, 250); - applyBlurToBackground(backgroundModeBeatmap); - }); + applyBlurToBackground(backgroundModeBeatmap); + }); + } beatmapInfoWedge.Beatmap = beatmap; From 5bdc5dfadde8c7b4d32abbd81e2555718f803366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 19:02:51 +0100 Subject: [PATCH 26/59] Add one more assert to keep coverage from previous implementation --- .../Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index e7bdf7b9ba..6770309a7d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -91,6 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestCorrectHitObject() { waitForAliveObjectIndex(null); + checkValidObjectIndex(0); seekBeforeIndex(0); waitForAliveObjectIndex(0); From ad5132ed4194380bae37331b2746333a5acead3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 20:47:51 +0100 Subject: [PATCH 27/59] Remove redundant conditional access qualifier It is impossible for the callback passed to `ApplyToBackground()` to receive a null reference. See `OsuScreen.ApplyToBackground()` - if the background to call the callback on were `null`, then an `InvalidOperationException` would be thrown instead. --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d9062da8aa..4f7e4add32 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -417,7 +417,7 @@ namespace osu.Game.Screens.Play lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) - ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); + ApplyToBackground(b => b.FadeColour(Color4.White, 800, Easing.OutQuint)); } protected virtual void ContentOut() From b8084a15ebc9a5bb278b46c7ea31d478063245e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 21:26:01 +0100 Subject: [PATCH 28/59] Revert `ResumeOverlay` setter accessibility change --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d57aec1271..2f8b101cfc 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.UI /// /// An optional overlay used when resuming gameplay from a paused state. /// - public ResumeOverlay ResumeOverlay { get; set; } + public ResumeOverlay ResumeOverlay { get; protected set; } /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. From a84f20bf32fbd4639571c4800a77b0233b04e70b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 15 Feb 2023 23:09:55 +0300 Subject: [PATCH 29/59] Add triangles to ModSelectColumn --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index e5154fd631..d9023e8dde 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -27,7 +28,12 @@ namespace osu.Game.Overlays.Mods public Color4 AccentColour { get => headerBackground.Colour; - set => headerBackground.Colour = value; + set + { + headerBackground.Colour = value; + var hsv = new Colour4(value.R, value.G, value.B, 1f).ToHSV(); + triangles.Colour = ColourInfo.GradientVertical(Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f), value); + } } /// @@ -44,6 +50,7 @@ namespace osu.Game.Overlays.Mods private readonly Box headerBackground; private readonly Container contentContainer; private readonly Box contentBackground; + private readonly TrianglesV2 triangles; private const float header_height = 42; @@ -73,6 +80,16 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Height = header_height + ModSelectPanel.CORNER_RADIUS }, + triangles = new TrianglesV2 + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 1.1f, + Height = header_height, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Masking = true + }, headerText = new OsuTextFlowContainer(t => { t.Font = OsuFont.TorusAlternate.With(size: 17); From 16d94b4ea2ac405a5988b6ebf11c5a7192a0d79f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 17:51:52 +0900 Subject: [PATCH 30/59] Improve visuals of skin blueprint --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 69 ++++++++++++------- .../Compose/Components/SelectionHandler.cs | 7 +- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 55ea362873..fedb1ac8e0 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -5,12 +5,15 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -29,35 +32,36 @@ namespace osu.Game.Overlays.SkinEditor protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; - [Resolved] - private OsuColour colours { get; set; } = null!; - public SkinBlueprint(ISerialisableDrawable component) : base(component) { } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { InternalChildren = new Drawable[] { box = new Container { + Padding = new MarginPadding(-SkinSelectionHandler.INFLATE_SIZE), Children = new Drawable[] { outlineBox = new Container { RelativeSizeAxes = Axes.Both, Masking = true, - BorderThickness = 3, - BorderColour = Color4.White, + CornerRadius = 3, + BorderThickness = SelectionBox.BORDER_RADIUS / 2, + BorderColour = ColourInfo.GradientVertical(colours.Pink4.Darken(0.4f), colours.Pink4), Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0f, + Blending = BlendingParameters.Additive, + Alpha = 0.2f, + Colour = ColourInfo.GradientVertical(colours.Pink2, colours.Pink4), AlwaysPresent = true, }, } @@ -100,9 +104,6 @@ namespace osu.Game.Overlays.SkinEditor private void updateSelectedState() { - outlineBox.FadeColour(colours.Pink.Opacity(IsSelected ? 1 : 0.5f), 200, Easing.OutQuint); - outlineBox.Child.FadeTo(IsSelected ? 0.2f : 0, 200, Easing.OutQuint); - anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint); } @@ -134,39 +135,47 @@ namespace osu.Game.Overlays.SkinEditor { private readonly Drawable drawable; - private readonly Box originBox; + private Drawable originBox = null!; - private readonly Box anchorBox; - private readonly Box anchorLine; + private Drawable anchorBox = null!; + private Drawable anchorLine = null!; public AnchorOriginVisualiser(Drawable drawable) { this.drawable = drawable; + } - InternalChildren = new Drawable[] + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Color4 anchorColour = colours.Red1; + Color4 originColour = colours.Red3; + + InternalChildren = new[] { - anchorLine = new Box + anchorLine = new Circle { - Height = 2, + Height = 3f, Origin = Anchor.CentreLeft, - Colour = Color4.Yellow, - EdgeSmoothness = Vector2.One + Colour = ColourInfo.GradientHorizontal(originColour.Opacity(0.5f), originColour), }, - originBox = new Box + originBox = new Circle { - Colour = Color4.Red, + Colour = originColour, Origin = Anchor.Centre, - Size = new Vector2(5), + Size = new Vector2(7), }, - anchorBox = new Box + anchorBox = new Circle { - Colour = Color4.Red, + Colour = anchorColour, Origin = Anchor.Centre, - Size = new Vector2(5), + Size = new Vector2(10), }, }; } + private Vector2? anchorPosition; + protected override void Update() { base.Update(); @@ -174,8 +183,18 @@ namespace osu.Game.Overlays.SkinEditor if (drawable.Parent == null) return; + var newAnchor = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); + + anchorPosition ??= newAnchor; + + anchorPosition = + new Vector2( + (float)Interpolation.DampContinuously(anchorPosition.Value.X, newAnchor.X, 25, Clock.ElapsedFrameTime), + (float)Interpolation.DampContinuously(anchorPosition.Value.Y, newAnchor.Y, 25, Clock.ElapsedFrameTime) + ); + originBox.Position = drawable.ToSpaceOfOtherDrawable(drawable.OriginPosition, this); - anchorBox.Position = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); + anchorBox.Position = anchorPosition.Value; var point1 = ToLocalSpace(anchorBox.ScreenSpaceDrawQuad.Centre); var point2 = ToLocalSpace(originBox.ScreenSpaceDrawQuad.Centre); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a0ac99fec2..9e4fb26688 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -32,6 +32,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IKeyBindingHandler, IHasContextMenu { + /// + /// How much padding around the selection area is added. + /// + public const float INFLATE_SIZE = 5; + /// /// The currently selected blueprints. /// Should be used when operations are dealing directly with the visible blueprints. @@ -346,7 +351,7 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int i = 1; i < selectedBlueprints.Count; i++) selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(selectedBlueprints[i].SelectionQuad).AABBFloat); - selectionRect = selectionRect.Inflate(5f); + selectionRect = selectionRect.Inflate(INFLATE_SIZE); SelectionBox.Position = selectionRect.Location; SelectionBox.Size = selectionRect.Size; From 6c61c5f4a871b68458487a348aef07c33147468d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 18:17:35 +0900 Subject: [PATCH 31/59] Fix selection on the edge of blueprints (in the new inflation area) failing --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index fedb1ac8e0..0ee9f35a62 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -32,6 +32,18 @@ namespace osu.Game.Overlays.SkinEditor protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; + private Quad drawableQuad; + + public override Quad ScreenSpaceDrawQuad => drawableQuad; + public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; + + public override bool Contains(Vector2 screenSpacePos) => drawableQuad.Contains(screenSpacePos); + + public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition); + + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => + drawableQuad.Contains(screenSpacePos); + public SkinBlueprint(ISerialisableDrawable component) : base(component) { @@ -44,7 +56,6 @@ namespace osu.Game.Overlays.SkinEditor { box = new Container { - Padding = new MarginPadding(-SkinSelectionHandler.INFLATE_SIZE), Children = new Drawable[] { outlineBox = new Container @@ -107,28 +118,21 @@ namespace osu.Game.Overlays.SkinEditor anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint); } - private Quad drawableQuad; - - public override Quad ScreenSpaceDrawQuad => drawableQuad; - protected override void Update() { base.Update(); - drawableQuad = drawable.ScreenSpaceDrawQuad; - var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad); + drawableQuad = drawable.ToScreenSpace( + drawable.DrawRectangle + .Inflate(SkinSelectionHandler.INFLATE_SIZE)); - box.Position = drawable.ToSpaceOfOtherDrawable(Vector2.Zero, this); - box.Size = quad.Size; + var localSpaceQuad = ToLocalSpace(drawableQuad); + + box.Position = localSpaceQuad.TopLeft; + box.Size = localSpaceQuad.Size; box.Rotation = drawable.Rotation; box.Scale = new Vector2(MathF.Sign(drawable.Scale.X), MathF.Sign(drawable.Scale.Y)); } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); - - public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition); - - public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; } internal partial class AnchorOriginVisualiser : CompositeDrawable From 814080d9823460fbf93c15ed9bcc1bf141b75308 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 17:56:41 +0900 Subject: [PATCH 32/59] Only show blueprint labels when hovering or selected --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 893bc4bac2..9d2c8368e7 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Edit; @@ -25,6 +26,8 @@ namespace osu.Game.Overlays.SkinEditor private AnchorOriginVisualiser anchorOriginVisualiser = null!; + private OsuSpriteText label = null!; + private Drawable drawable => (Drawable)Item; protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; @@ -62,7 +65,7 @@ namespace osu.Game.Overlays.SkinEditor }, } }, - new OsuSpriteText + label = new OsuSpriteText { Text = Item.GetType().Name, Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), @@ -86,6 +89,18 @@ namespace osu.Game.Overlays.SkinEditor this.FadeInFromZero(200, Easing.OutQuint); } + protected override bool OnHover(HoverEvent e) + { + updateSelectedState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateSelectedState(); + base.OnHoverLost(e); + } + protected override void OnSelected() { // base logic hides selected blueprints when not selected, but skin blueprints don't do that. @@ -104,6 +119,7 @@ namespace osu.Game.Overlays.SkinEditor outlineBox.Child.FadeTo(IsSelected ? 0.2f : 0, 200, Easing.OutQuint); anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint); + label.FadeTo(IsSelected || IsHovered ? 1 : 0, 200, Easing.OutQuint); } private Quad drawableQuad; From 5ed038fbb3b0cddf07a460223312ea52c2ca3d1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Feb 2023 16:44:48 +0900 Subject: [PATCH 33/59] Improve the feel of hovering toolbox component items --- .../SkinEditor/SkinComponentToolbox.cs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 28ceaf09fc..624841a3bc 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Logging; -using osu.Game.Graphics; +using osu.Framework.Threading; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; @@ -65,7 +66,8 @@ namespace osu.Game.Overlays.SkinEditor fill.Add(new ToolboxComponentButton(instance, target) { - RequestPlacement = t => RequestPlacement?.Invoke(t) + RequestPlacement = t => RequestPlacement?.Invoke(t), + Expanding = contractOtherButtons, }); } catch (DependencyNotRegisteredException) @@ -79,15 +81,29 @@ namespace osu.Game.Overlays.SkinEditor } } + private void contractOtherButtons(ToolboxComponentButton obj) + { + foreach (var b in fill.OfType()) + { + if (b == obj) + continue; + + b.Contract(); + } + } + public partial class ToolboxComponentButton : OsuButton { public Action? RequestPlacement; + public Action? Expanding; private readonly Drawable component; private readonly CompositeDrawable? dependencySource; private Container innerContainer = null!; + private ScheduledDelegate? expandContractAction; + private const float contracted_size = 60; private const float expanded_size = 120; @@ -102,20 +118,44 @@ namespace osu.Game.Overlays.SkinEditor Height = contracted_size; } + private const double animation_duration = 500; + protected override bool OnHover(HoverEvent e) { - this.Delay(300).ResizeHeightTo(expanded_size, 500, Easing.OutQuint); + expandContractAction = Scheduler.AddDelayed(() => + { + this.ResizeHeightTo(expanded_size, animation_duration, Easing.OutQuint); + Expanding?.Invoke(this); + }, 100); + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - this.ResizeHeightTo(contracted_size, 500, Easing.OutQuint); + + expandContractAction?.Cancel(); + // If no other component is selected for too long, force a contract. + // Otherwise we will generally contract when Contract() is called from outside. + expandContractAction = Scheduler.AddDelayed(Contract, 1000); + } + + public void Contract() + { + // Cheap debouncing to avoid stacking animations. + // The only place this is nulled is at the end of this method. + if (expandContractAction == null) + return; + + this.ResizeHeightTo(contracted_size, animation_duration, Easing.OutQuint); + + expandContractAction?.Cancel(); + expandContractAction = null; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider) { BackgroundColour = colourProvider.Background3; From 0838fa636fc8dd3fba186383c45deb469efc220a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 17 Feb 2023 15:16:00 +0300 Subject: [PATCH 34/59] Make triangles slower --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index d9023e8dde..843f978c13 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -88,6 +88,7 @@ namespace osu.Game.Overlays.Mods Width = 1.1f, Height = header_height, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Velocity = 0.7f, Masking = true }, headerText = new OsuTextFlowContainer(t => From 51940133df8f938356295c017a4677058aea358b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 17 Feb 2023 15:18:45 +0300 Subject: [PATCH 35/59] Adjust width and add comment --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 843f978c13..41a6cbd549 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, - Width = 1.1f, + Width = 1.03f, // Makes sure the sheared area is fully covered Height = header_height, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Velocity = 0.7f, From dbb366e2792e0224cff5d5a0fffc84316b7dc76c Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 17 Feb 2023 22:32:03 +0900 Subject: [PATCH 36/59] CompletionText can be a LocalisableString I can't find a reason for not doing this, probably this was forgotten in https://github.com/ppy/osu/pull/15440 --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 5cce0f8c5b..e6662e2179 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Notifications } } - public string CompletionText { get; set; } = "Task has completed!"; + public LocalisableString CompletionText { get; set; } = "Task has completed!"; private float progress; From 449e5fa6f82d10a9006db2ed5d96ffbe9ebd0a26 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Feb 2023 22:09:52 +0300 Subject: [PATCH 37/59] Rename one more left-over `skinnable` naming --- osu.Game/Skinning/SerialisedDrawableInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index 12f6d3ac7e..29078f0d74 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -64,8 +64,8 @@ namespace osu.Game.Skinning Anchor = component.Anchor; Origin = component.Origin; - if (component is ISerialisableDrawable skinnable) - UsesFixedAnchor = skinnable.UsesFixedAnchor; + if (component is ISerialisableDrawable serialisableDrawable) + UsesFixedAnchor = serialisableDrawable.UsesFixedAnchor; foreach (var (_, property) in component.GetSettingsSourceProperties()) { From ffcca9fd89db75ddf9b28bc00f3de59cdc4d5c78 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 17 Feb 2023 23:23:58 +0300 Subject: [PATCH 38/59] Remove awkward width specification --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 41a6cbd549..e6d7bcd97d 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -31,8 +31,10 @@ namespace osu.Game.Overlays.Mods set { headerBackground.Colour = value; + var hsv = new Colour4(value.R, value.G, value.B, 1f).ToHSV(); - triangles.Colour = ColourInfo.GradientVertical(Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f), value); + var trianglesColour = Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f); + triangles.Colour = ColourInfo.GradientVertical(trianglesColour, trianglesColour.MultiplyAlpha(0f)); } } @@ -82,14 +84,10 @@ namespace osu.Game.Overlays.Mods }, triangles = new TrianglesV2 { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, - Width = 1.03f, // Makes sure the sheared area is fully covered Height = header_height, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Velocity = 0.7f, - Masking = true }, headerText = new OsuTextFlowContainer(t => { From b390fdb8ccb46f8f257cce6c1a9af7b6281b05c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Feb 2023 21:51:19 +0100 Subject: [PATCH 39/59] Remove unused field --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 0ee9f35a62..2cb14814d1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -24,8 +24,6 @@ namespace osu.Game.Overlays.SkinEditor { private Container box = null!; - private Container outlineBox = null!; - private AnchorOriginVisualiser anchorOriginVisualiser = null!; private Drawable drawable => (Drawable)Item; @@ -58,7 +56,7 @@ namespace osu.Game.Overlays.SkinEditor { Children = new Drawable[] { - outlineBox = new Container + new Container { RelativeSizeAxes = Axes.Both, Masking = true, From 2aa4481f6802feed215633d62c43044aa5ef38cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Feb 2023 22:54:11 +0100 Subject: [PATCH 40/59] Fix toolbox items spontaneously contracting after briefly losing hover Reproduction scenario: 1. Hover a toolbox item 2. Unhover the item, but do not hover any other item (can be done by exiting the toolbox completely to the right) 3. Come back to the item hovered in step (1) 4. The item would spontaneously contract after a second --- osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 624841a3bc..f5726bf427 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -122,6 +122,7 @@ namespace osu.Game.Overlays.SkinEditor protected override bool OnHover(HoverEvent e) { + expandContractAction?.Cancel(); expandContractAction = Scheduler.AddDelayed(() => { this.ResizeHeightTo(expanded_size, animation_duration, Easing.OutQuint); From ddd37bb3190419cc93b9ea49e4ad14e205b61efb Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sat, 18 Feb 2023 19:43:45 +0100 Subject: [PATCH 41/59] Add setting to disable automatic seeking after object placement --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Localisation/EditorStrings.cs | 5 +++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++++++- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 565a919fb8..a4544200c7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -178,6 +178,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorShowHitMarkers, true); + SetDefault(OsuSetting.EditorSeekToHitobject, true); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -374,6 +375,7 @@ namespace osu.Game.Configuration SeasonalBackgroundMode, EditorWaveformOpacity, EditorShowHitMarkers, + EditorSeekToHitobject, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 96c08aa6f8..65cecd27d6 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers"); + /// + /// "Seek to Object after placement" + /// + public static LocalisableString SeekToHitobject => new TranslatableString(getKey(@"seek_to_hitobject"), @"Seek to Object after placement"); + /// /// "Timing" /// diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b5b7400f64..528088dbda 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -17,6 +17,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; @@ -70,6 +71,7 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; private IBindable hasTiming; + protected Bindable SeekToHitobject { get; private set; } protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -80,8 +82,10 @@ namespace osu.Game.Rulesets.Edit dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { + SeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); + Config = Dependencies.Get().GetConfigFor(Ruleset); try @@ -365,6 +369,9 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); + // conditionally seek based on setting + if (!SeekToHitobject.Value) return; + if (EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bd133383d1..d6c698c139 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -185,6 +185,7 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; + private Bindable editorSeekToHitobject; public Editor(EditorLoader loader = null) { @@ -272,6 +273,7 @@ namespace osu.Game.Screens.Edit editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim); editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); + editorSeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); AddInternal(new OsuContextMenuContainer { @@ -329,6 +331,10 @@ namespace osu.Game.Screens.Edit new ToggleMenuItem(EditorStrings.ShowHitMarkers) { State = { BindTarget = editorHitMarkers }, + }, + new ToggleMenuItem(EditorStrings.SeekToHitobject) + { + State = { BindTarget = editorSeekToHitobject }, } } }, From 55e9a71f388a7c5aed00f8fadcb1ce76bd725a73 Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sat, 18 Feb 2023 20:42:13 +0100 Subject: [PATCH 42/59] Add test for seeking setting in mania placement test --- .../TestScenePlacementBeforeTrackStart.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs index 00dd75ceee..4c48a361b8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs @@ -3,8 +3,11 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK.Input; @@ -14,6 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); + [Resolved] + private OsuConfigManager config { get; set; } = null!; + [Test] public void TestPlacement() { @@ -26,5 +32,36 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("Click", () => InputManager.Click(MouseButton.Left)); AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0)); } + + [Test] + public void TestSeekOnNotePlacement() + { + AddStep("Seek to 1935", () => EditorClock.Seek(1935)); + AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitobject, true)); + seekSetup(); + AddUntilStep("Wait for seeking to end", () => !EditorClock.IsSeeking); + AddAssert("Seeked to object", () => + { + return EditorClock.CurrentTimeAccurate == 2287.1875; + }); + } + + [Test] + public void TestNoSeekOnNotePlacement() + { + AddStep("Seek to 1935", () => EditorClock.Seek(1935)); + AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitobject, false)); + seekSetup(); + AddAssert("Not seeking", () => !EditorClock.IsSeeking); + AddAssert("Not seeked to object", () => EditorClock.CurrentTime == 1935); + } + + private void seekSetup() + { + AddStep("Seek to 1935", () => EditorClock.Seek(1935)); + AddStep("Select note", () => InputManager.Key(Key.Number2)); + AddStep("Place note", () => InputManager.MoveMouseTo(this.ChildrenOfType().First(x => x.HitObject.StartTime == 2170))); + AddStep("Click", () => InputManager.Click(MouseButton.Left)); + } } } From 025061ba66766fad6a1b3521693956c09d69592e Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sun, 19 Feb 2023 10:17:33 +0100 Subject: [PATCH 43/59] fix formating in SeekOnNote test --- .../Editor/TestScenePlacementBeforeTrackStart.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs index 4c48a361b8..142fa5ce07 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs @@ -37,20 +37,17 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public void TestSeekOnNotePlacement() { AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitobject, true)); + AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); seekSetup(); AddUntilStep("Wait for seeking to end", () => !EditorClock.IsSeeking); - AddAssert("Seeked to object", () => - { - return EditorClock.CurrentTimeAccurate == 2287.1875; - }); + AddAssert("Seeked to object", () => EditorClock.CurrentTimeAccurate == 2287.1875); } [Test] public void TestNoSeekOnNotePlacement() { AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitobject, false)); + AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); seekSetup(); AddAssert("Not seeking", () => !EditorClock.IsSeeking); AddAssert("Not seeked to object", () => EditorClock.CurrentTime == 1935); From f3522c41629ea1f4ba1cde94b4b4b1509d7074c9 Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sun, 19 Feb 2023 10:18:02 +0100 Subject: [PATCH 44/59] change bindable seekToHitObject to private --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 528088dbda..f989c81da8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; private IBindable hasTiming; - protected Bindable SeekToHitobject { get; private set; } + private Bindable seekToHitObject; protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { - SeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); + seekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); Config = Dependencies.Get().GetConfigFor(Ruleset); @@ -369,8 +369,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - // conditionally seek based on setting - if (!SeekToHitobject.Value) return; + if (!seekToHitObject.Value) return; if (EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); From 723a043c4351da62b8c16913b42d3e27f2e1b843 Mon Sep 17 00:00:00 2001 From: Maximilian Kruse Date: Sun, 19 Feb 2023 10:18:22 +0100 Subject: [PATCH 45/59] naming change from Hitobject to HitObject --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/EditorStrings.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a4544200c7..9093c017d2 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -178,7 +178,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorShowHitMarkers, true); - SetDefault(OsuSetting.EditorSeekToHitobject, true); + SetDefault(OsuSetting.EditorSeekToHitObject, true); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -375,7 +375,7 @@ namespace osu.Game.Configuration SeasonalBackgroundMode, EditorWaveformOpacity, EditorShowHitMarkers, - EditorSeekToHitobject, + EditorSeekToHitObject, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 65cecd27d6..4557cb532f 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -20,9 +20,9 @@ namespace osu.Game.Localisation public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers"); /// - /// "Seek to Object after placement" + /// "Seek to object after placement" /// - public static LocalisableString SeekToHitobject => new TranslatableString(getKey(@"seek_to_hitobject"), @"Seek to Object after placement"); + public static LocalisableString SeekToHitObject => new TranslatableString(getKey(@"seek_to_hit_object"), @"Seek to object after placement"); /// /// "Timing" diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d6c698c139..e9a0fbf6fa 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; - private Bindable editorSeekToHitobject; + private Bindable editorSeekToHitObject; public Editor(EditorLoader loader = null) { @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Edit editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim); editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); - editorSeekToHitobject = config.GetBindable(OsuSetting.EditorSeekToHitobject); + editorSeekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); AddInternal(new OsuContextMenuContainer { @@ -332,9 +332,9 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorHitMarkers }, }, - new ToggleMenuItem(EditorStrings.SeekToHitobject) + new ToggleMenuItem(EditorStrings.SeekToHitObject) { - State = { BindTarget = editorSeekToHitobject }, + State = { BindTarget = editorSeekToHitObject }, } } }, From aac32a2c9f49795e60a2f397bf833060299f2d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:14:51 +0100 Subject: [PATCH 46/59] Combine config and time checks into one Functionally equivalent right now, but the combined variant is more localised to what it actually needs to do, and less error-prone if any new code gets appended to the method. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f989c81da8..eb10d09560 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -369,9 +369,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - if (!seekToHitObject.Value) return; - - if (EditorClock.CurrentTime < hitObject.StartTime) + if (seekToHitObject.Value && EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); } } From 80b329f06924952df70fcb926cf2cc4fdaaf6fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:16:40 +0100 Subject: [PATCH 47/59] Rename test scene to match contents It does not only test "placement before track start" anymore. --- ...PlacementBeforeTrackStart.cs => TestSceneObjectPlacement.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Mania.Tests/Editor/{TestScenePlacementBeforeTrackStart.cs => TestSceneObjectPlacement.cs} (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index 142fa5ce07..02980f3ac8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestScenePlacementBeforeTrackStart.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -13,7 +13,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Mania.Tests.Editor { - public partial class TestScenePlacementBeforeTrackStart : EditorTestScene + public partial class TestSceneObjectPlacement : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); From 80ee917c7780e0e5da061312312430ead795f44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:33:06 +0100 Subject: [PATCH 48/59] Rewrite test cases - Depend less on arbitrary timings - Remove unnecessary seeks - Change method name to make more sense - Use nunit style assertions --- .../Editor/TestSceneObjectPlacement.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index 02980f3ac8..ec2995924d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -7,7 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Input; @@ -36,29 +36,32 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestSeekOnNotePlacement() { - AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); - seekSetup(); - AddUntilStep("Wait for seeking to end", () => !EditorClock.IsSeeking); - AddAssert("Seeked to object", () => EditorClock.CurrentTimeAccurate == 2287.1875); + double? initialTime = null; + + AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); + AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); + placeObject(); + AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking); + AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime)); } [Test] public void TestNoSeekOnNotePlacement() { - AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); - seekSetup(); - AddAssert("Not seeking", () => !EditorClock.IsSeeking); - AddAssert("Not seeked to object", () => EditorClock.CurrentTime == 1935); + double? initialTime = null; + + AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); + AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); + placeObject(); + AddAssert("not seeking", () => !EditorClock.IsSeeking); + AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime)); } - private void seekSetup() + private void placeObject() { - AddStep("Seek to 1935", () => EditorClock.Seek(1935)); - AddStep("Select note", () => InputManager.Key(Key.Number2)); - AddStep("Place note", () => InputManager.MoveMouseTo(this.ChildrenOfType().First(x => x.HitObject.StartTime == 2170))); - AddStep("Click", () => InputManager.Click(MouseButton.Left)); + AddStep("select note placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to centre of last column", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last().ScreenSpaceDrawQuad.Centre)); + AddStep("place note", () => InputManager.Click(MouseButton.Left)); } } } From 8b25598d8251b0459528ea69053b25bcb62f923b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 13:54:48 +0100 Subject: [PATCH 49/59] Rename moved test method to describe its purpose better --- .../Editor/TestSceneObjectPlacement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index ec2995924d..6ddcabfc4d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private OsuConfigManager config { get; set; } = null!; [Test] - public void TestPlacement() + public void TestPlacementBeforeTrackStart() { AddStep("Seek to 0", () => EditorClock.Seek(0)); AddStep("Select note", () => InputManager.Key(Key.Number2)); From d9ca7102f048e1deb0ba79493866998043cde238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Feb 2023 15:06:40 +0100 Subject: [PATCH 50/59] Use more generic wording for future-proofing --- .../Editor/TestSceneObjectPlacement.cs | 4 ++-- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/EditorStrings.cs | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +++--- osu.Game/Screens/Edit/Editor.cs | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs index 6ddcabfc4d..13a116b209 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor double? initialTime = null; AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); - AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorSeekToHitObject, true)); + AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, true)); placeObject(); AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking); AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime)); @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor double? initialTime = null; AddStep("store initial time", () => initialTime = EditorClock.CurrentTime); - AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorSeekToHitObject, false)); + AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, false)); placeObject(); AddAssert("not seeking", () => !EditorClock.IsSeeking); AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime)); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9093c017d2..70ad6bfc96 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -178,7 +178,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorShowHitMarkers, true); - SetDefault(OsuSetting.EditorSeekToHitObject, true); + SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -375,7 +375,7 @@ namespace osu.Game.Configuration SeasonalBackgroundMode, EditorWaveformOpacity, EditorShowHitMarkers, - EditorSeekToHitObject, + EditorAutoSeekOnPlacement, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 4557cb532f..f4e23ae7cb 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -20,9 +20,9 @@ namespace osu.Game.Localisation public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers"); /// - /// "Seek to object after placement" + /// "Automatically seek after placing objects" /// - public static LocalisableString SeekToHitObject => new TranslatableString(getKey(@"seek_to_hit_object"), @"Seek to object after placement"); + public static LocalisableString AutoSeekOnPlacement => new TranslatableString(getKey(@"auto_seek_on_placement"), @"Automatically seek after placing objects"); /// /// "Timing" diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index eb10d09560..aee86fd942 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Edit private FillFlowContainer togglesCollection; private IBindable hasTiming; - private Bindable seekToHitObject; + private Bindable autoSeekOnPlacement; protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { - seekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); + autoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); Config = Dependencies.Get().GetConfigFor(Ruleset); @@ -369,7 +369,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - if (seekToHitObject.Value && EditorClock.CurrentTime < hitObject.StartTime) + if (autoSeekOnPlacement.Value && EditorClock.CurrentTime < hitObject.StartTime) EditorClock.SeekSmoothlyTo(hitObject.StartTime); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e9a0fbf6fa..d89392f757 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; - private Bindable editorSeekToHitObject; + private Bindable editorAutoSeekOnPlacement; public Editor(EditorLoader loader = null) { @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Edit editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim); editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); - editorSeekToHitObject = config.GetBindable(OsuSetting.EditorSeekToHitObject); + editorAutoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); AddInternal(new OsuContextMenuContainer { @@ -332,9 +332,9 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorHitMarkers }, }, - new ToggleMenuItem(EditorStrings.SeekToHitObject) + new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) { - State = { BindTarget = editorSeekToHitObject }, + State = { BindTarget = editorAutoSeekOnPlacement }, } } }, From d7381b762ca33dffb6980f9d92e5e3eec5c269d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 19 Feb 2023 23:52:21 +0900 Subject: [PATCH 51/59] Also tween origin position --- osu.Game/Overlays/SkinEditor/SkinBlueprint.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs index 2cb14814d1..f5814b4c01 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprint.cs @@ -177,6 +177,7 @@ namespace osu.Game.Overlays.SkinEditor } private Vector2? anchorPosition; + private Vector2? originPositionInDrawableSpace; protected override void Update() { @@ -186,18 +187,13 @@ namespace osu.Game.Overlays.SkinEditor return; var newAnchor = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); - - anchorPosition ??= newAnchor; - - anchorPosition = - new Vector2( - (float)Interpolation.DampContinuously(anchorPosition.Value.X, newAnchor.X, 25, Clock.ElapsedFrameTime), - (float)Interpolation.DampContinuously(anchorPosition.Value.Y, newAnchor.Y, 25, Clock.ElapsedFrameTime) - ); - - originBox.Position = drawable.ToSpaceOfOtherDrawable(drawable.OriginPosition, this); + anchorPosition = tweenPosition(anchorPosition ?? newAnchor, newAnchor); anchorBox.Position = anchorPosition.Value; + // for the origin, tween in the drawable's local space to avoid unwanted tweening when the drawable is being dragged. + originPositionInDrawableSpace = originPositionInDrawableSpace != null ? tweenPosition(originPositionInDrawableSpace.Value, drawable.OriginPosition) : drawable.OriginPosition; + originBox.Position = drawable.ToSpaceOfOtherDrawable(originPositionInDrawableSpace.Value, this); + var point1 = ToLocalSpace(anchorBox.ScreenSpaceDrawQuad.Centre); var point2 = ToLocalSpace(originBox.ScreenSpaceDrawQuad.Centre); @@ -205,5 +201,11 @@ namespace osu.Game.Overlays.SkinEditor anchorLine.Width = (point2 - point1).Length; anchorLine.Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)); } + + private Vector2 tweenPosition(Vector2 oldPosition, Vector2 newPosition) + => new Vector2( + (float)Interpolation.DampContinuously(oldPosition.X, newPosition.X, 25, Clock.ElapsedFrameTime), + (float)Interpolation.DampContinuously(oldPosition.Y, newPosition.Y, 25, Clock.ElapsedFrameTime) + ); } } From c86c1a902984a4e9c67b8b070b4b18565eb25dc1 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 20 Feb 2023 00:06:20 -0500 Subject: [PATCH 52/59] allow tablet area to be dragged --- .../Sections/Input/TabletAreaSelection.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 6f7faf535b..8b15bc8f72 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Utils; using osu.Game.Graphics; @@ -66,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input RelativeSizeAxes = Axes.Both, Colour = colour.Gray1, }, - usableAreaContainer = new Container + usableAreaContainer = new UsableAreaContainer(handler) { Origin = Anchor.Centre, Children = new Drawable[] @@ -225,4 +226,28 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletContainer.Scale = new Vector2(1 / adjust); } } + + public partial class UsableAreaContainer : Container + { + private readonly Bindable areaOffset; + + public UsableAreaContainer(ITabletHandler tabletHandler) + { + areaOffset = tabletHandler.AreaOffset.GetBoundCopy(); + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override void OnDrag(DragEvent e) + { + var newPos = Position + e.Delta; + this.MoveTo(Vector2.Clamp(newPos, Vector2.Zero, Parent.Size)); + } + + protected override void OnDragEnd(DragEndEvent e) + { + areaOffset.Value = Position; + base.OnDragEnd(e); + } + } } From 5f7a6d13c35fb898cc9f72bf82c574499cff3a03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Feb 2023 19:47:17 +0900 Subject: [PATCH 53/59] Remove unused `GetSerialisableIdentifier` for now --- osu.Game/Skinning/SkinComponentsContainerLookup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Skinning/SkinComponentsContainerLookup.cs b/osu.Game/Skinning/SkinComponentsContainerLookup.cs index f6c462ddaa..c7fc30f724 100644 --- a/osu.Game/Skinning/SkinComponentsContainerLookup.cs +++ b/osu.Game/Skinning/SkinComponentsContainerLookup.cs @@ -17,8 +17,6 @@ namespace osu.Game.Skinning public readonly RulesetInfo? Ruleset; - public string GetSerialisableIdentifier() => Ruleset?.ShortName ?? "global"; - public SkinComponentsContainerLookup(TargetArea target, RulesetInfo? ruleset = null) { Target = target; From 0ddda018fdd0c8ee1b47906848f6c7e3f3344a21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Feb 2023 19:48:18 +0900 Subject: [PATCH 54/59] Add xmldoc for `SkinComponentsContainerLookup.Ruleset` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Skinning/SkinComponentsContainerLookup.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Skinning/SkinComponentsContainerLookup.cs b/osu.Game/Skinning/SkinComponentsContainerLookup.cs index c7fc30f724..812ba5d495 100644 --- a/osu.Game/Skinning/SkinComponentsContainerLookup.cs +++ b/osu.Game/Skinning/SkinComponentsContainerLookup.cs @@ -15,6 +15,10 @@ namespace osu.Game.Skinning /// public readonly TargetArea Target; + /// + /// The ruleset for which skin components should be returned. + /// A value means that returned components are global and should be applied for all rulesets. + /// public readonly RulesetInfo? Ruleset; public SkinComponentsContainerLookup(TargetArea target, RulesetInfo? ruleset = null) From 1629c86b5d55caf1ad7d8083ed78015066322a65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Feb 2023 19:48:39 +0900 Subject: [PATCH 55/59] Mark constant identifier as non-localisable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Skinning/SkinLayoutInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinLayoutInfo.cs b/osu.Game/Skinning/SkinLayoutInfo.cs index ff27a47223..3b91987b7c 100644 --- a/osu.Game/Skinning/SkinLayoutInfo.cs +++ b/osu.Game/Skinning/SkinLayoutInfo.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning [Serializable] public class SkinLayoutInfo { - private const string global_identifier = "global"; + private const string global_identifier = @"global"; public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v); From 18700b4daa2c061f2f9798c0954003cd71450da1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Feb 2023 19:51:54 +0900 Subject: [PATCH 56/59] Add note about skin migrations being on read and remove an older deprecation notice --- osu.Game/Skinning/Skin.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index b55ddf250a..ad7c3bdff1 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -125,11 +125,15 @@ namespace osu.Game.Skinning { } + // Of note, the migration code below runs on read of skins, but there's nothing to + // force a rewrite after migration. Let's not remove these migration rules until we + // have something in place to ensure we don't end up breaking skins of users that haven't + // manually saved their skin since a change was implemented. + // if that fails, attempt to deserialise using the old naked list. if (layoutInfo == null) { // handle namespace changes... - // can be removed 2023-01-31 jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); From ec12186d6333988cb58c3113de9468750b7a1a5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Feb 2023 19:53:04 +0900 Subject: [PATCH 57/59] Remove unnecesasry null check on `content` --- osu.Game/Skinning/SkinComponentsContainer.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/SkinComponentsContainer.cs b/osu.Game/Skinning/SkinComponentsContainer.cs index d4e0b2e69b..d18e9023cd 100644 --- a/osu.Game/Skinning/SkinComponentsContainer.cs +++ b/osu.Game/Skinning/SkinComponentsContainer.cs @@ -74,17 +74,12 @@ namespace osu.Game.Skinning cancellationSource?.Cancel(); cancellationSource = null; - if (content != null) + LoadComponentAsync(content, wrapper => { - LoadComponentAsync(content, wrapper => - { - AddInternal(wrapper); - components.AddRange(wrapper.Children.OfType()); - ComponentsLoaded = true; - }, (cancellationSource = new CancellationTokenSource()).Token); - } - else + AddInternal(wrapper); + components.AddRange(wrapper.Children.OfType()); ComponentsLoaded = true; + }, (cancellationSource = new CancellationTokenSource()).Token); } /// From 43724472c4c7a793c58e57c3083895d5e3e7ee76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Feb 2023 20:07:17 +0100 Subject: [PATCH 58/59] Clarify comment to avoid playing pronoun game --- osu.Game/Skinning/Skin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index ad7c3bdff1..a6250d7488 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -130,7 +130,7 @@ namespace osu.Game.Skinning // have something in place to ensure we don't end up breaking skins of users that haven't // manually saved their skin since a change was implemented. - // if that fails, attempt to deserialise using the old naked list. + // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list. if (layoutInfo == null) { // handle namespace changes... From 86a7f4dfd0f4f7bf389e0177c326c748b99f0fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Feb 2023 20:33:54 +0100 Subject: [PATCH 59/59] Do not serialise `SkinLayoutInfo.AllDrawables` - It is entirely derived from `SkinLayoutInfo.DrawableInfo`, which is the actual primary thing we want to serialise. - It will never get read out from any serialised files anyway (corollary of the previous point - it is a get-only property derived from another). - It is only used in tests. All of the three reasons above make serialising the property out to skin files nothing more than a waste of space. --- osu.Game/Skinning/SkinLayoutInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/SkinLayoutInfo.cs b/osu.Game/Skinning/SkinLayoutInfo.cs index 3b91987b7c..115d59b9d0 100644 --- a/osu.Game/Skinning/SkinLayoutInfo.cs +++ b/osu.Game/Skinning/SkinLayoutInfo.cs @@ -19,6 +19,7 @@ namespace osu.Game.Skinning { private const string global_identifier = @"global"; + [JsonIgnore] public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v); [JsonProperty]