From 81485c548c84226d4e6f59e9b7f221b376eb449d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Sep 2023 14:19:26 +0900 Subject: [PATCH 01/37] Move `LegacyLastTickOffset` specification to generation code and stop passing everywhere --- .../Beatmaps/CatchBeatmapConverter.cs | 1 - .../Objects/JuiceStream.cs | 4 +--- .../Editor/TestSceneSliderSplitting.cs | 1 - .../Beatmaps/OsuBeatmapConverter.cs | 1 - .../Sliders/SliderSelectionBlueprint.cs | 1 - .../Mods/OsuModStrictTracking.cs | 5 ++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++--- .../Objects/SliderTailCircle.cs | 2 +- .../Beatmaps/SliderEventGenerationTest.cs | 16 +++++++------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 4 +--- .../Rulesets/Objects/SliderEventGenerator.cs | 22 ++++++++++++++----- .../Objects/Types/IHasLegacyLastTickOffset.cs | 14 ------------ 12 files changed, 32 insertions(+), 45 deletions(-) delete mode 100644 osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 5e8a0b1216..6a24c26844 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps X = xPositionData?.X ?? 0, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0, LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y, SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1 }.Yield(); diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 724bdc3401..87d316d36d 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Objects int nodeIndex = 0; SliderEventDescriptor? lastEvent = null; - foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), cancellationToken)) { // generate tiny droplets since the last point if (lastEvent != null) @@ -162,7 +162,5 @@ namespace osu.Game.Rulesets.Catch.Objects public double Distance => Path.Distance; public IList> NodeSamples { get; set; } = new List>(); - - public double? LegacyLastTickOffset { get; set; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index 8ba97892fe..7315344295 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -163,7 +163,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider = new Slider { Position = new Vector2(0, 50), - LegacyLastTickOffset = 36, // This is necessary for undo to retain the sample control point Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index f3aaf831d3..3c051a6bb1 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.Beatmaps Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset, // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // this results in more (or less) ticks being generated in s.With()).ToList(), RepeatCount = HitObject.RepeatCount, NodeSamples = HitObject.NodeSamples.Select(n => (IList)n.Select(s => s.With()).ToList()).ToList(), diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index 9b5d405025..78062a0632 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -96,14 +96,13 @@ namespace osu.Game.Rulesets.Osu.Mods Position = original.Position; NewCombo = original.NewCombo; ComboOffset = original.ComboOffset; - LegacyLastTickOffset = original.LegacyLastTickOffset; TickDistanceMultiplier = original.TickDistanceMultiplier; SliderVelocityMultiplier = original.SliderVelocityMultiplier; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); + var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), cancellationToken); foreach (var e in sliderEvents) { @@ -130,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Mods }); break; - case SliderEventType.LegacyLastTick: + case SliderEventType.LastTick: AddNested(TailCircle = new StrictTrackingSliderTailCircle(this) { RepeatIndex = e.SpanIndex, diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e05dbd8ea6..9736c69e17 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Osu.Objects } } - public double? LegacyLastTickOffset { get; set; } - /// /// The position of the cursor at the point of completion of this if it was hit /// with as few movements as possible. This is set and used by difficulty calculation. @@ -179,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects { base.CreateNestedHitObjects(cancellationToken); - var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); + var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), cancellationToken); foreach (var e in sliderEvents) { @@ -206,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Objects }); break; - case SliderEventType.LegacyLastTick: + case SliderEventType.LastTick: // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. // if this is to change, we should revisit this. diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index b4574791d2..fb81936837 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects { /// /// Note that this should not be used for timing correctness. - /// See usage in for more information. + /// See usage in for more information. /// public class SliderTailCircle : SliderEndCircle { diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs index a26c8121dd..37a91c8611 100644 --- a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs +++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestSingleSpan() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestRepeat() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestNonEvenTicks() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -83,12 +83,12 @@ namespace osu.Game.Tests.Beatmaps } [Test] - public void TestLegacyLastTickOffset() + public void TestLastTickOffset() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1).ToArray(); - Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); - Assert.That(events[2].Time, Is.EqualTo(900)); + Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LastTick)); + Assert.That(events[2].Time, Is.EqualTo(span_duration + SliderEventGenerator.LAST_TICK_OFFSET)); } [Test] @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps const double velocity = 5; const double min_distance = velocity * 10; - var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2).ToArray(); Assert.Multiple(() => { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index d91ecf956a..ab8fd2c662 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -13,7 +13,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset, IHasSliderVelocity + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity { /// /// Scoring distance with a speed-adjusted beat length of 1 second. @@ -59,7 +59,5 @@ namespace osu.Game.Rulesets.Objects.Legacy Velocity = scoringDistance / timingPoint.BeatLength; } - - public double LegacyLastTickOffset => 36; } } diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 7013d32cbc..b3477a5fde 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -10,9 +10,17 @@ namespace osu.Game.Rulesets.Objects { public static class SliderEventGenerator { - // ReSharper disable once MethodOverloadWithOptionalParameter + /// + /// Historically, slider's final tick (aka the place where the slider would receive a final judgement) was offset by -36 ms. Originally this was + /// done to workaround a technical detail (unimportant), but over the years it has become an expectation of players that you don't need to hold + /// until the true end of the slider. This very small amount of leniency makes it easier to jump away from fast sliders to the next hit object. + /// + /// After discussion on how this should be handled going forward, players have unanimously stated that this lenience should remain in some way. + /// + public const double LAST_TICK_OFFSET = -36; + public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, - double? legacyLastTickOffset, CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default) { // A very lenient maximum length of a slider for ticks to be generated. // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. @@ -76,14 +84,14 @@ namespace osu.Game.Rulesets.Objects int finalSpanIndex = spanCount - 1; double finalSpanStartTime = startTime + finalSpanIndex * spanDuration; - double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0)); + double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) + LAST_TICK_OFFSET); double finalProgress = (finalSpanEndTime - finalSpanStartTime) / spanDuration; if (spanCount % 2 == 0) finalProgress = 1 - finalProgress; yield return new SliderEventDescriptor { - Type = SliderEventType.LegacyLastTick, + Type = SliderEventType.LastTick, SpanIndex = finalSpanIndex, SpanStartTime = finalSpanStartTime, Time = finalSpanEndTime, @@ -173,7 +181,11 @@ namespace osu.Game.Rulesets.Objects public enum SliderEventType { Tick, - LegacyLastTick, + + /// + /// Occurs just before the tail. See . + /// + LastTick, Head, Tail, Repeat diff --git a/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs b/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs deleted file mode 100644 index caf22c3023..0000000000 --- a/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A type of which may require the last tick to be offset. - /// This is specific to osu!stable conversion, and should not be used elsewhere. - /// - public interface IHasLegacyLastTickOffset - { - double LegacyLastTickOffset { get; } - } -} From d7119674e8b5063d5350fbed78046aa873865a45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Sep 2023 14:40:44 +0900 Subject: [PATCH 02/37] Update comments to better explain what `LastTick` is doing --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModSynesthesia.cs | 2 +- .../Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 11 +++++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 87d316d36d..fb1a86d8c0 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Catch.Objects } } - // this also includes LegacyLastTick and this is used for TinyDroplet generation above. - // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied. + // this also includes LastTick and this is used for TinyDroplet generation above. + // this means that the final segment of TinyDroplets are increasingly mistimed where LastTick is being applied. lastEvent = e; switch (e.Type) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSynesthesia.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSynesthesia.cs index 9537f8b388..e1123807cd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSynesthesia.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSynesthesia.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods d.HitObjectApplied += _ => { - // slider tails are a painful edge case, as their start time is offset 36ms back (see `LegacyLastTick`). + // slider tails are a painful edge case, as their start time is offset 36ms back (see `LastTick`). // to work around this, look up the slider tail's parenting slider's end time instead to ensure proper snap. double snapTime = d is DrawableSliderTail tail ? tail.Slider.GetEndTime() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 1a6a0a9ecc..77e60a1690 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override void PlaySamples() { // rather than doing it this way, we should probably attach the sample to the tail circle. - // this can only be done after we stop using LegacyLastTick. + // this can only be done if we stop using LastTick. if (!TailCircle.SamplePlaysOnlyOnHit || TailCircle.IsHit) base.PlaySamples(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9736c69e17..443e4229d2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -205,9 +205,10 @@ namespace osu.Game.Rulesets.Osu.Objects break; case SliderEventType.LastTick: - // we need to use the LegacyLastTick here for compatibility reasons (difficulty). - // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. - // if this is to change, we should revisit this. + // Of note, we are directly mapping LastTick (instead of `SliderEventType.Tail`) to SliderTailCircle. + // It is required as difficulty calculation and gameplay relies on reading this value. + // (although it is displayed in classic skins, which may be a concern). + // If this is to change, we should revisit this. AddNested(TailCircle = new SliderTailCircle(this) { RepeatIndex = e.SpanIndex, @@ -262,7 +263,9 @@ namespace osu.Game.Rulesets.Osu.Objects if (HeadCircle != null) HeadCircle.Samples = this.GetNodeSamples(0); - // The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to. + // The samples should be attached to the slider tail, however this can only be done if LastTick is removed otherwise they would play earlier than they're intended to. + // (see mapping logic in `CreateNestedHitObjects` above) + // // For now, the samples are played by the slider itself at the correct end time. TailSamples = this.GetNodeSamples(repeatCount + 1); } From 869f8e5e1b21dfe53bb56a54a8479797956358f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 00:20:53 +0300 Subject: [PATCH 03/37] Adjust test scene to apply miss judgements as well --- .../Gameplay/TestSceneSkinnableHealthDisplay.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index b4ebb7c410..e3700a2a8b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -28,15 +29,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep(@"Reset all", delegate { healthProcessor.Health.Value = 1; + healthProcessor.Failed += () => false; // health won't be updated if the processor gets into a "fail" state. }); } [Test] public void TestHealthDisplayIncrementing() { - AddRepeatStep(@"decrease hp", delegate + AddRepeatStep("apply miss judgement", delegate { - healthProcessor.Health.Value -= 0.08f; + healthProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }); + }, 5); + + AddRepeatStep(@"decrease hp slightly", delegate + { + healthProcessor.Health.Value -= 0.01f; }, 10); AddRepeatStep(@"increase hp without flash", delegate From 33b0cb15a88ece545779bad5ac754f1a79275398 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 00:21:12 +0300 Subject: [PATCH 04/37] Add handling for miss judgements in `HealthDisplay` --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 9fdd735804..5131f93ca2 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -29,10 +29,22 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1 }; + /// + /// Triggered when a is a successful hit, signaling the health display to perform a flash animation (if designed to do so). + /// + /// The judgement result. protected virtual void Flash(JudgementResult result) { } + /// + /// Triggered when a resulted in the player losing health. + /// + /// The judgement result. + protected virtual void Miss(JudgementResult result) + { + } + [Resolved] private HUDOverlay? hudOverlay { get; set; } @@ -54,6 +66,8 @@ namespace osu.Game.Screens.Play.HUD { if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) Flash(judgement); + else if (judgement.Judgement.HealthIncreaseFor(judgement) < 0) + Miss(judgement); } protected override void Dispose(bool isDisposing) From 776536e816075e179a595e42c56c275f989e039b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 26 Sep 2023 02:23:29 +0300 Subject: [PATCH 05/37] Add "Argon" health display implementation --- .../TestSceneSkinnableHealthDisplay.cs | 3 +- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 284 ++++++++++++++++++ 2 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index e3700a2a8b..4d8ddcd581 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -20,6 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay(); protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay(); protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay(); @@ -61,4 +62,4 @@ namespace osu.Game.Tests.Visual.Gameplay }, 3); } } -} +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs new file mode 100644 index 0000000000..ca7a4a6cf7 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -0,0 +1,284 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Framework.Utils; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + public partial class ArgonHealthDisplay : HealthDisplay, ISerialisableDrawable + { + private const float curve_start = 280; + private const float curve_end = 310; + private const float curve_smoothness = 10; + + private const float bar_length = 350; + private const float bar_height = 32.5f; + + private BarPath healthBar = null!; + private BarPath missBar = null!; + + private SliderPath barPath = null!; + + private static readonly Colour4 health_bar_colour = Colour4.White; + + // the opacity isn't part of the design, it's only here to control glow intensity. + private static readonly Colour4 health_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.3f); + private static readonly Colour4 health_bar_flash_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.4f); + + private static readonly Colour4 miss_bar_colour = Color4Extensions.FromHex("#FF9393"); + private static readonly Colour4 miss_bar_glow_colour = Color4Extensions.FromHex("#FD0000"); + + // the "flashed" glow colour is just a lightened version of the original one, not part of the design. + private static readonly Colour4 miss_bar_flash_colour = Color4Extensions.FromHex("#FF5D5D"); + + public bool UsesFixedAnchor { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Anchor = Anchor.TopLeft; + Origin = Anchor.TopLeft; + AutoSizeAxes = Axes.Both; + + Vector2 diagonalDir = (new Vector2(curve_end, bar_height) - new Vector2(curve_start, 0)).Normalized(); + + // todo: SliderPath or parts of it should be moved away to a utility class as they're useful for making curved paths in general, as done here. + barPath = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(curve_start - curve_smoothness, 0), PathType.Bezier), + new PathControlPoint(new Vector2(curve_start, 0)), + new PathControlPoint(new Vector2(curve_start, 0) + diagonalDir * curve_smoothness, PathType.Linear), + new PathControlPoint(new Vector2(curve_end, bar_height) - diagonalDir * curve_smoothness, PathType.Bezier), + new PathControlPoint(new Vector2(curve_end, bar_height)), + new PathControlPoint(new Vector2(curve_end + curve_smoothness, bar_height), PathType.Linear), + new PathControlPoint(new Vector2(bar_length, bar_height)), + }); + + var vertices = new List(); + barPath.GetPathToProgress(vertices, 0.0, 1.0); + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4f, 0f), + Children = new Drawable[] + { + new Circle + { + Margin = new MarginPadding { Top = 10f - 3f / 2f, Left = -2f }, + Size = new Vector2(50f, 3f), + }, + new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new BackgroundPath + { + PathRadius = 10f, + Vertices = vertices, + }, + missBar = new BarPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + BarColour = miss_bar_colour, + GlowColour = miss_bar_glow_colour, + Alpha = 0f, + PathRadius = 10f, + Vertices = vertices + }, + healthBar = new BarPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + BarColour = health_bar_colour, + GlowColour = health_bar_glow_colour, + PathRadius = 10f, + Vertices = vertices + }, + } + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(v => + { + if (v.NewValue > MissBarValue) + { + missBar.FadeOut(300, Easing.OutQuint); + resetMissBarDelegate?.Cancel(); + resetMissBarDelegate = null; + } + + if (v.NewValue == 0) + healthBar.FadeOut(300, Easing.OutQuint); + else if (healthBar.Alpha < 1) + healthBar.FadeIn(300, Easing.OutQuint); + + this.TransformTo(nameof(HealthBarValue), v.NewValue, 300, Easing.OutQuint); + }, true); + } + + private ScheduledDelegate? resetMissBarDelegate; + + protected override void Miss(JudgementResult result) + { + base.Miss(result); + + if (resetMissBarDelegate != null) + resetMissBarDelegate.Cancel(); + else + this.TransformTo(nameof(MissBarValue), HealthBarValue); + + this.Delay(500).Schedule(() => + { + this.TransformTo(nameof(MissBarValue), Current.Value, 300, Easing.OutQuint); + resetMissBarDelegate = null; + }, out resetMissBarDelegate); + + missBar.FadeIn(120, Easing.OutQuint); + missBar.Delay(500).FadeOut(300, Easing.InQuint); + + missBar.TransformTo(nameof(BarPath.BarColour), miss_bar_colour.Lighten(0.1f)) + .TransformTo(nameof(BarPath.BarColour), miss_bar_colour, 300, Easing.OutQuint); + + missBar.TransformTo(nameof(BarPath.GlowColour), miss_bar_flash_colour) + .TransformTo(nameof(BarPath.GlowColour), miss_bar_glow_colour, 300, Easing.OutQuint); + } + + protected override void Flash(JudgementResult result) + { + base.Flash(result); + + healthBar.TransformTo(nameof(BarPath.GlowColour), health_bar_flash_colour) + .TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 300, Easing.OutQuint); + } + + private double missBarValue = 1.0; + private readonly List missBarVertices = new List(); + + public double MissBarValue + { + get => missBarValue; + set + { + if (missBarValue == value) + return; + + missBarValue = value; + updatePathVertices(); + } + } + + private double healthBarValue = 1.0; + private readonly List healthBarVertices = new List(); + + public double HealthBarValue + { + get => healthBarValue; + set + { + if (healthBarValue == value) + return; + + healthBarValue = value; + updatePathVertices(); + } + } + + private void updatePathVertices() + { + barPath.GetPathToProgress(healthBarVertices, 0.0, healthBarValue); + barPath.GetPathToProgress(missBarVertices, healthBarValue, Math.Max(missBarValue, healthBarValue)); + + if (healthBarVertices.Count == 0) + healthBarVertices.Add(Vector2.Zero); + + if (missBarVertices.Count == 0) + missBarVertices.Add(Vector2.Zero); + + missBar.Vertices = missBarVertices.Select(v => v - missBarVertices[0]).ToList(); + missBar.Position = missBarVertices[0]; + + healthBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList(); + healthBar.Position = healthBarVertices[0]; + } + + private partial class BackgroundPath : SmoothPath + { + protected override Color4 ColourAt(float position) + { + if (position <= 0.128f) + return Color4.White.Opacity(0.3f); + + position -= 0.128f; + return Interpolation.ValueAt(Math.Clamp(position, 0f, 1f), Color4.White.Opacity(0.5f), Color4.Black.Opacity(0.5f), -0.75f, 1f, Easing.OutQuart); + } + } + + private partial class BarPath : SmoothPath + { + private Colour4 barColour; + + public Colour4 BarColour + { + get => barColour; + set + { + if (barColour == value) + return; + + barColour = value; + InvalidateTexture(); + } + } + + private Colour4 glowColour; + + public Colour4 GlowColour + { + get => glowColour; + set + { + if (glowColour == value) + return; + + glowColour = value; + InvalidateTexture(); + } + } + + protected override Color4 ColourAt(float position) + { + if (position >= 0.6f) + return BarColour; + + return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, 0.6); + } + } + } +} From 319208ca3d985b1e733adc39c0be321e09f26f67 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 01:18:05 +0300 Subject: [PATCH 06/37] Adjust health bar glow intensity --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index ca7a4a6cf7..5035571820 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -38,8 +38,8 @@ namespace osu.Game.Screens.Play.HUD private static readonly Colour4 health_bar_colour = Colour4.White; // the opacity isn't part of the design, it's only here to control glow intensity. - private static readonly Colour4 health_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.3f); - private static readonly Colour4 health_bar_flash_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.4f); + private static readonly Colour4 health_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.5f); + private static readonly Colour4 health_bar_flash_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.6f); private static readonly Colour4 miss_bar_colour = Color4Extensions.FromHex("#FF9393"); private static readonly Colour4 miss_bar_glow_colour = Color4Extensions.FromHex("#FD0000"); From a331fb993ae5dbc94b50283ffb7577d7efb08332 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 01:21:37 +0300 Subject: [PATCH 07/37] Adjust health bar outer stroke colour --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 5035571820..ed141a9a26 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -233,7 +233,7 @@ namespace osu.Game.Screens.Play.HUD protected override Color4 ColourAt(float position) { if (position <= 0.128f) - return Color4.White.Opacity(0.3f); + return Color4.White.Opacity(0.5f); position -= 0.128f; return Interpolation.ValueAt(Math.Clamp(position, 0f, 1f), Color4.White.Opacity(0.5f), Color4.Black.Opacity(0.5f), -0.75f, 1f, Easing.OutQuart); From 9e2b8254d9fc95986c1c3a7eebffe91d8b4f4b0b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 13:26:43 +0300 Subject: [PATCH 08/37] Add argon-specific health display test scene --- .../Gameplay/TestSceneArgonHealthDisplay.cs | 78 +++++++++++++++++++ .../Screens/Play/HUD/ArgonHealthDisplay.cs | 2 - 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs new file mode 100644 index 0000000000..12a2611a76 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneArgonHealthDisplay : OsuTestScene + { + [Cached(typeof(HealthProcessor))] + private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep(@"Reset all", delegate + { + healthProcessor.Health.Value = 1; + healthProcessor.Failed += () => false; // health won't be updated if the processor gets into a "fail" state. + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + new ArgonHealthDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2f), + }, + }; + }); + } + + [Test] + public void TestHealthDisplayIncrementing() + { + AddRepeatStep("apply miss judgement", delegate + { + healthProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }); + }, 5); + + AddRepeatStep(@"decrease hp slightly", delegate + { + healthProcessor.Health.Value -= 0.01f; + }, 10); + + AddRepeatStep(@"increase hp without flash", delegate + { + healthProcessor.Health.Value += 0.1f; + }, 3); + + AddRepeatStep(@"increase hp with flash", delegate + { + healthProcessor.Health.Value += 0.1f; + healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement()) + { + Type = HitResult.Perfect + }); + }, 3); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index ed141a9a26..2db8e9d641 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -52,8 +52,6 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load() { - Anchor = Anchor.TopLeft; - Origin = Anchor.TopLeft; AutoSizeAxes = Axes.Both; Vector2 diagonalDir = (new Vector2(curve_end, bar_height) - new Vector2(curve_start, 0)).Normalized(); From 446c9c2efefa819fc835cc8a2bb266311f40babf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 13:39:36 +0300 Subject: [PATCH 09/37] Apply adjustments on the "miss" bar display --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 2db8e9d641..aeaaa19f33 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -96,21 +96,20 @@ namespace osu.Game.Screens.Play.HUD }, missBar = new BarPath { - AutoSizeAxes = Axes.None, - RelativeSizeAxes = Axes.Both, BarColour = miss_bar_colour, GlowColour = miss_bar_glow_colour, Alpha = 0f, - PathRadius = 10f, + PathRadius = 20f, + GlowPortion = 0.75f, + Margin = new MarginPadding(-10f), Vertices = vertices }, healthBar = new BarPath { - AutoSizeAxes = Axes.None, - RelativeSizeAxes = Axes.Both, BarColour = health_bar_colour, GlowColour = health_bar_glow_colour, PathRadius = 10f, + GlowPortion = 0.6f, Vertices = vertices }, } @@ -270,9 +269,11 @@ namespace osu.Game.Screens.Play.HUD } } + public float GlowPortion { get; init; } + protected override Color4 ColourAt(float position) { - if (position >= 0.6f) + if (position >= GlowPortion) return BarColour; return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, 0.6); From 30d9004ef9bc089f543bdbb68f3ac6ea7dbb99f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 13:41:11 +0300 Subject: [PATCH 10/37] Fix small mistake --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index aeaaa19f33..434736d42a 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -276,7 +276,7 @@ namespace osu.Game.Screens.Play.HUD if (position >= GlowPortion) return BarColour; - return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, 0.6); + return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion); } } } From 8d389accf81ceb8b77eb6a9287419d1ffd689ab0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 15:12:24 +0300 Subject: [PATCH 11/37] Cover "Argon" helath display in skin deserialisation tests --- .../Archives/modified-argon-pro-20231001.osk | Bin 0 -> 1834 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-pro-20231001.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231001.osk b/osu.Game.Tests/Resources/Archives/modified-argon-pro-20231001.osk new file mode 100644 index 0000000000000000000000000000000000000000..081bb73b9ef7bd85d763007117d4f18fe4046fa4 GIT binary patch literal 1834 zcmZ{k3pA8z7{@<%)ey@Fsfdu68N@O+iKb-6WyVab%KbJjGni2(8%CSUG(_(A+me+_ ziILp0V%-gPkc@<|xh7Ji-KlN2V|Mp@&vV}MzTf+Q&htFye|}a-K7L660E7WK$|)PI zcV5S;IHz!)Pd5NS^2QS&cmlqs@4hI{DKWu`ketaohy7y{qUNx0aFS|7mW#`kGjHDp zy!P3BYZ@$Smo^f2!b!H7i0*HOnLKal1odvLdfaWum7i11S|deZ`$cdw-`G`MP+Z^@!Tl z)?(GoIf3eOvd@+~`c6WdzHWe&?~~M%hv&5c$#>p5RV|c#(*NyE7Oot5syTIj$08)d_{*uu*6;b+f*{n`n%xYNrhorUla) zz%tu=EIUwLDD!@7RD@<=^D8p7giNK;MY=&6&PimW8U1~Q%E{CFsB}z@?GE?}{M1w^Vf*V2g478z8ZRn*waaV#7+R)VKERS-<6WktPXW5mv zG7d)b0)QD90Hio&n>*nNrnu84L|;E5!HqyBeXf7e22IR>iG?gz7*Q0u>%5FXW*!V1X|bChMk_R%Vx1*oSP-6C>7=Bjg%s zNekEREs3}@Q@i2k!bTgU3M1q!qliJezj4j&$u_A7?;y=OziC8srup25=RJl zQ2Cdl@!d_@VcG%hOvWO*iO><#x*pZgJXGy>2Q#R45#5{MB#ss>JNJr!3z~)Y-$Eps zs>`+MtO-@7f@M^6RxVOEyO$hmDej2c@T-Vi9f|vDv=WsxWja)x7Pc=%sE;-hQHM|x zzuc3>t}HEQBZg!>BRvgb^FjE;<@DO-CS`+RcKYM10#$a)jvc>MzsL_t849q^`duRT zqRNBFs$kq)wg4PoP~&}RUE1ZS6y^yjQ8OG9?B+93f6zlWtv>TX9{Y?PAKN}bExzUy zeGRTD6(duhCy%rjf#RCj)p92W$p?PT3JS$6ymxgDDQIAfJI&XSMl}7iVcWaVlUHDp zYOC?ie3ZT(79L#k25!8aq~T(EYA#HhhiYL#PsapoOLG=p$+j+y6mGqU$r*1vlSc&x{ zZx7*_%hE9~Yby;D$Mep%Wlzx?u3>6gbB7lmLZ`|;H2tvhDlN9%6JHLPv=wY@I&SaZ zY2K`8w3Q5pB-$JU@8y`+nn>`lcJpy_A%AwQZ@i^<3k=k{ye-)_>v&$5p*82^8Y*p2 zTFQpF38~;DtZ};;Ep}#&TMKb+_+5sn8wu-cah4Ja(*>-PaFd`yjjmw--RewU{Xyxq zN38rQU*Et|d8|^MUBldo;+MT*GHTe)E!wVjgX-|>p~fRZ@lj1F(eHTYL4zO6s#V1C zF^vqEN^t`#H42fH*YSMf;p72krq}7T4|^T%X&dUTp$$TWmYTlcFieh&Yg2zDh zh9mR6^QAZlDF&wN1lLlyhu#qCL^57Cv%?xMSW7?pDMxhdf&Swn;;0uHM&}=o1lM(8 z4Hxu7&fI`U-uCA^A0!11zbyZq-tzSK(;C)3sj==B$m{#}Xg=SpYcUT#%R;3)?3|)p zlJiw%&h(CQLX;C$K28*Oyqk~fS9Ap&HB+}lb)H-AMt0d$Vu}T6Jcu_pk<`pNTltFQ zyHXse@bF3k|Ic|&+P^;Pr|oa*bE~)+`b7f()X^`he~8L0<>uH|X*|au|4lXS9Nc#L mn!|$sU*_T#b5HTBI8lImrdCJ+!B77MI19olZ-(;%0R99!8uHBm literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 82d204f134..98008a003d 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -52,7 +52,9 @@ namespace osu.Game.Tests.Skins // Covers player avatar and flag. "Archives/modified-argon-20230305.osk", // Covers key counters - "Archives/modified-argon-pro-20230618.osk" + "Archives/modified-argon-pro-20230618.osk", + // Covers "Argon" health display + "Archives/modified-argon-pro-20231001.osk" }; /// From 7825bea959f41e6e1df8f14ed32eacd124ba0a20 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 15:25:23 +0300 Subject: [PATCH 12/37] Use interpolation for health bar opacity instead of transforms --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 434736d42a..c6c64ebcad 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -131,15 +131,18 @@ namespace osu.Game.Screens.Play.HUD resetMissBarDelegate = null; } - if (v.NewValue == 0) - healthBar.FadeOut(300, Easing.OutQuint); - else if (healthBar.Alpha < 1) - healthBar.FadeIn(300, Easing.OutQuint); - this.TransformTo(nameof(HealthBarValue), v.NewValue, 300, Easing.OutQuint); }, true); } + protected override void Update() + { + base.Update(); + + float targetAlpha = Current.Value > 0 ? 1 : 0; + healthBar.Alpha = (float)Interpolation.DampContinuously(healthBar.Alpha, targetAlpha, 50.0, Time.Elapsed); + } + private ScheduledDelegate? resetMissBarDelegate; protected override void Miss(JudgementResult result) From eef099e69d347e9502d63142d8920f3edf74e332 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 1 Oct 2023 15:25:34 +0300 Subject: [PATCH 13/37] Do not display "miss" bar if health is already zero --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index c6c64ebcad..50ac100ed2 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -149,6 +149,10 @@ namespace osu.Game.Screens.Play.HUD { base.Miss(result); + if (result.HealthAtJudgement == 0.0) + // health is already empty, nothing should be displayed here. + return; + if (resetMissBarDelegate != null) resetMissBarDelegate.Cancel(); else From 22aa7ffd06af80f021023d5a97162d6f06adc7a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Oct 2023 13:45:16 +0900 Subject: [PATCH 14/37] Use additive colour --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 50ac100ed2..023c5e47da 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -98,6 +98,7 @@ namespace osu.Game.Screens.Play.HUD { BarColour = miss_bar_colour, GlowColour = miss_bar_glow_colour, + Blending = BlendingParameters.Additive, Alpha = 0f, PathRadius = 20f, GlowPortion = 0.75f, @@ -106,6 +107,9 @@ namespace osu.Game.Screens.Play.HUD }, healthBar = new BarPath { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, BarColour = health_bar_colour, GlowColour = health_bar_glow_colour, PathRadius = 10f, From df51e612342a0aadecc86267df90c9e641a694de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Oct 2023 21:20:03 +0900 Subject: [PATCH 15/37] Improve animation --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 023c5e47da..1419ab1b43 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -7,11 +7,13 @@ using System.Linq; 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.Lines; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -96,13 +98,13 @@ namespace osu.Game.Screens.Play.HUD }, missBar = new BarPath { - BarColour = miss_bar_colour, - GlowColour = miss_bar_glow_colour, + BarColour = Color4.White, + GlowColour = OsuColour.Gray(0.5f), Blending = BlendingParameters.Additive, - Alpha = 0f, - PathRadius = 20f, - GlowPortion = 0.75f, - Margin = new MarginPadding(-10f), + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), + PathRadius = 40f, + GlowPortion = 0.9f, + Margin = new MarginPadding(-30f), Vertices = vertices }, healthBar = new BarPath @@ -130,7 +132,8 @@ namespace osu.Game.Screens.Play.HUD { if (v.NewValue > MissBarValue) { - missBar.FadeOut(300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 300, Easing.OutQuint); resetMissBarDelegate?.Cancel(); resetMissBarDelegate = null; } @@ -158,19 +161,23 @@ namespace osu.Game.Screens.Play.HUD return; if (resetMissBarDelegate != null) + { resetMissBarDelegate.Cancel(); + resetMissBarDelegate = null; + } else this.TransformTo(nameof(MissBarValue), HealthBarValue); this.Delay(500).Schedule(() => { this.TransformTo(nameof(MissBarValue), Current.Value, 300, Easing.OutQuint); + + missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 300, Easing.OutQuint); + resetMissBarDelegate = null; }, out resetMissBarDelegate); - missBar.FadeIn(120, Easing.OutQuint); - missBar.Delay(500).FadeOut(300, Easing.InQuint); - missBar.TransformTo(nameof(BarPath.BarColour), miss_bar_colour.Lighten(0.1f)) .TransformTo(nameof(BarPath.BarColour), miss_bar_colour, 300, Easing.OutQuint); @@ -287,7 +294,7 @@ namespace osu.Game.Screens.Play.HUD if (position >= GlowPortion) return BarColour; - return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion); + return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion, Easing.InQuart); } } } From 88d608e1fab112e12cb31660191df51c93f69fd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 00:42:47 +0900 Subject: [PATCH 16/37] Tidy up common animation code --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 1419ab1b43..c64d065e50 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -131,14 +131,11 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(v => { if (v.NewValue > MissBarValue) - { - missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 300, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 300, Easing.OutQuint); - resetMissBarDelegate?.Cancel(); - resetMissBarDelegate = null; - } + finishMissBarUsage(); this.TransformTo(nameof(HealthBarValue), v.NewValue, 300, Easing.OutQuint); + if (resetMissBarDelegate == null) + this.TransformTo(nameof(MissBarValue), v.NewValue, 300, Easing.OutQuint); }, true); } @@ -146,8 +143,8 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); - float targetAlpha = Current.Value > 0 ? 1 : 0; - healthBar.Alpha = (float)Interpolation.DampContinuously(healthBar.Alpha, targetAlpha, 50.0, Time.Elapsed); + healthBar.Alpha = (float)Interpolation.DampContinuously(healthBar.Alpha, (float)(Current.Value > 0 ? 1 : 0), 40, Time.Elapsed); + missBar.Alpha = (float)Interpolation.DampContinuously(missBar.Alpha, (float)(MissBarValue > 0 ? 1 : 0), 40, Time.Elapsed); } private ScheduledDelegate? resetMissBarDelegate; @@ -172,10 +169,7 @@ namespace osu.Game.Screens.Play.HUD { this.TransformTo(nameof(MissBarValue), Current.Value, 300, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 300, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White, 300, Easing.OutQuint); - - resetMissBarDelegate = null; + finishMissBarUsage(); }, out resetMissBarDelegate); missBar.TransformTo(nameof(BarPath.BarColour), miss_bar_colour.Lighten(0.1f)) @@ -185,6 +179,18 @@ namespace osu.Game.Screens.Play.HUD .TransformTo(nameof(BarPath.GlowColour), miss_bar_glow_colour, 300, Easing.OutQuint); } + private void finishMissBarUsage() + { + if (Current.Value > 0) + { + missBar.TransformTo(nameof(BarPath.BarColour), Colour4.Gray, 300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.Gray, 300, Easing.OutQuint); + } + + resetMissBarDelegate?.Cancel(); + resetMissBarDelegate = null; + } + protected override void Flash(JudgementResult result) { base.Flash(result); From c4f47974bc044a63ec393375b4de5c2cea4a4eb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 00:56:29 +0900 Subject: [PATCH 17/37] Improve glow further --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index c64d065e50..97ae2cc7ec 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play.HUD if (Current.Value > 0) { missBar.TransformTo(nameof(BarPath.BarColour), Colour4.Gray, 300, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.Gray, 300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 300, Easing.OutQuint); } resetMissBarDelegate?.Cancel(); @@ -300,7 +300,7 @@ namespace osu.Game.Screens.Play.HUD if (position >= GlowPortion) return BarColour; - return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion, Easing.InQuart); + return Interpolation.ValueAt(position, Colour4.Black.Opacity(0.0f), GlowColour, 0.0, GlowPortion, Easing.InQuint); } } } From 3a45bcad15857029647b56a462b7de21522e25fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 02:03:45 +0900 Subject: [PATCH 18/37] Improve flash and glow further --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 97ae2cc7ec..6cf1daa102 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD // the opacity isn't part of the design, it's only here to control glow intensity. private static readonly Colour4 health_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.5f); - private static readonly Colour4 health_bar_flash_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.6f); + private static readonly Colour4 health_bar_flash_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.8f); private static readonly Colour4 miss_bar_colour = Color4Extensions.FromHex("#FF9393"); private static readonly Colour4 miss_bar_glow_colour = Color4Extensions.FromHex("#FD0000"); @@ -130,13 +130,15 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(v => { - if (v.NewValue > MissBarValue) + if (v.NewValue >= MissBarValue) finishMissBarUsage(); this.TransformTo(nameof(HealthBarValue), v.NewValue, 300, Easing.OutQuint); if (resetMissBarDelegate == null) this.TransformTo(nameof(MissBarValue), v.NewValue, 300, Easing.OutQuint); }, true); + + updatePathVertices(); } protected override void Update() @@ -172,19 +174,20 @@ namespace osu.Game.Screens.Play.HUD finishMissBarUsage(); }, out resetMissBarDelegate); - missBar.TransformTo(nameof(BarPath.BarColour), miss_bar_colour.Lighten(0.1f)) - .TransformTo(nameof(BarPath.BarColour), miss_bar_colour, 300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.BarColour), miss_bar_colour, 100, Easing.OutQuint) + .Then() + .TransformTo(nameof(BarPath.BarColour), miss_bar_flash_colour, 800, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), miss_bar_flash_colour) - .TransformTo(nameof(BarPath.GlowColour), miss_bar_glow_colour, 300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.GlowColour), miss_bar_glow_colour.Lighten(0.2f)) + .TransformTo(nameof(BarPath.GlowColour), miss_bar_glow_colour, 800, Easing.OutQuint); } private void finishMissBarUsage() { if (Current.Value > 0) { - missBar.TransformTo(nameof(BarPath.BarColour), Colour4.Gray, 300, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 300, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.BarColour), health_bar_colour, 300, Easing.In); + missBar.TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 300, Easing.In); } resetMissBarDelegate?.Cancel(); @@ -197,6 +200,16 @@ namespace osu.Game.Screens.Play.HUD healthBar.TransformTo(nameof(BarPath.GlowColour), health_bar_flash_colour) .TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 300, Easing.OutQuint); + + if (resetMissBarDelegate == null) + { + missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 100, Easing.OutQuint) + .Then() + .TransformTo(nameof(BarPath.BarColour), health_bar_colour, 800, Easing.OutQuint); + + missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White) + .TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 800, Easing.OutQuint); + } } private double missBarValue = 1.0; From 7dd9951c448515bb438992ca4aa127a3f770e604 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 17:41:08 +0900 Subject: [PATCH 19/37] Split out reverse arrow implementations to allow better animation --- .../Objects/Drawables/DrawableSliderRepeat.cs | 8 +++- .../Skinning/Argon/ArgonReverseArrow.cs | 48 ++++++++++++++----- ...seArrowPiece.cs => DefaultReverseArrow.cs} | 13 ++--- .../Skinning/Legacy/LegacyReverseArrow.cs | 18 ++++++- 4 files changed, 63 insertions(+), 24 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/Default/{ReverseArrowPiece.cs => DefaultReverseArrow.cs} (78%) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 5721328057..ac4d733672 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SkinnableDrawable CirclePiece { get; private set; } - public ReverseArrowPiece Arrow { get; private set; } + public SkinnableDrawable Arrow { get; private set; } private Drawable scaleContainer; @@ -65,7 +65,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - Arrow = new ReverseArrowPiece(), + Arrow = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow), _ => new DefaultReverseArrow()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, } }); diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs index 67fc1b2304..61077e08a4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -2,12 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -15,14 +19,19 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public partial class ArgonReverseArrow : CompositeDrawable + public partial class ArgonReverseArrow : BeatSyncedContainer { + [Resolved] + private DrawableHitObject drawableRepeat { get; set; } = null!; + private Bindable accentColour = null!; private SpriteIcon icon = null!; + private Container main = null!; + [BackgroundDependencyLoader] - private void load(DrawableHitObject hitObject) + private void load(TextureStore textures, DrawableHitObject hitObject) { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -31,24 +40,39 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon InternalChildren = new Drawable[] { - new Circle + main = new Container { - Size = new Vector2(40, 20), - Colour = Color4.White, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - icon = new SpriteIcon - { - Icon = FontAwesome.Solid.AngleDoubleRight, - Size = new Vector2(16), + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, + Children = new Drawable[] + { + new Circle + { + Size = new Vector2(40, 20), + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + icon = new SpriteIcon + { + Icon = FontAwesome.Solid.AngleDoubleRight, + Size = new Vector2(16), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } }, }; accentColour = hitObject.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true); } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (!drawableRepeat.Judged) + main.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs similarity index 78% rename from osu.Game.Rulesets.Osu/Skinning/Default/ReverseArrowPiece.cs rename to osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs index 27868db2f6..251f048fb6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs @@ -9,17 +9,16 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public partial class ReverseArrowPiece : BeatSyncedContainer + public partial class DefaultReverseArrow : BeatSyncedContainer { [Resolved] private DrawableHitObject drawableRepeat { get; set; } = null!; - public ReverseArrowPiece() + public DefaultReverseArrow() { Divisor = 2; MinimumBeatLength = 200; @@ -29,14 +28,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Size = OsuHitObject.OBJECT_DIMENSIONS; - Child = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon + Child = new SpriteIcon { RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(0.35f) - }) - { + Size = new Vector2(0.35f), Anchor = Anchor.Centre, Origin = Anchor.Centre, }; @@ -44,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { - if (!drawableRepeat.IsHit) + if (!drawableRepeat.Judged) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index 3a80607522..c63ae35a3f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -3,9 +3,11 @@ using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -14,8 +16,11 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public partial class LegacyReverseArrow : CompositeDrawable + public partial class LegacyReverseArrow : BeatSyncedContainer { + [Resolved] + private DrawableHitObject drawableRepeat { get; set; } = null!; + [Resolved(canBeNull: true)] private DrawableHitObject? drawableHitObject { get; set; } @@ -30,6 +35,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skinSource) { + Divisor = 2; + MinimumBeatLength = 200; + AutoSizeAxes = Axes.Both; string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName; @@ -59,6 +67,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } } + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (!drawableRepeat.Judged) + Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + } + private void onHitObjectApplied(DrawableHitObject drawableObject) { Debug.Assert(proxy.Parent == null); From 455fc228ad5c572a772648a7da53084ea034dd5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 18:26:35 +0900 Subject: [PATCH 20/37] Add edge piece to argon reverse arrow --- .../Skinning/Argon/ArgonReverseArrow.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs index 61077e08a4..aed7d1a55f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -29,10 +29,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private SpriteIcon icon = null!; private Container main = null!; + private Sprite side = null!; [BackgroundDependencyLoader] private void load(TextureStore textures, DrawableHitObject hitObject) { + Divisor = 2; + MinimumBeatLength = 120; + EarlyActivationMilliseconds = 30; + Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -63,6 +68,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon }, } }, + side = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = textures.Get("Gameplay/osu/repeat-edge-piece"), + Size = new Vector2(ArgonMainCirclePiece.OUTER_GRADIENT_SIZE), + } }; accentColour = hitObject.AccentColour.GetBoundCopy(); @@ -72,7 +84,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!drawableRepeat.Judged) - main.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + { + main.ScaleTo(1.3f, 30, Easing.Out) + .Then() + .ScaleTo(1f, timingPoint.BeatLength / 2, Easing.Out); + side + .MoveToX(-12, 30, Easing.Out) + .Then() + .MoveToX(0, timingPoint.BeatLength / 2, Easing.Out); + } } } } From ad4e988520def03498327625be3c9369be0d0a7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 18:34:21 +0900 Subject: [PATCH 21/37] Adjust reverse arrows to bounce more --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs | 7 +++---- .../Skinning/Default/DefaultReverseArrow.cs | 2 +- .../Skinning/Legacy/LegacyReverseArrow.cs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs index aed7d1a55f..1ce3a32900 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -35,8 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private void load(TextureStore textures, DrawableHitObject hitObject) { Divisor = 2; - MinimumBeatLength = 120; - EarlyActivationMilliseconds = 30; + MinimumBeatLength = 150; Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -85,11 +84,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { if (!drawableRepeat.Judged) { - main.ScaleTo(1.3f, 30, Easing.Out) + main.ScaleTo(1.3f, timingPoint.BeatLength / 8, Easing.Out) .Then() .ScaleTo(1f, timingPoint.BeatLength / 2, Easing.Out); side - .MoveToX(-12, 30, Easing.Out) + .MoveToX(-12, timingPoint.BeatLength / 8, Easing.Out) .Then() .MoveToX(0, timingPoint.BeatLength / 2, Easing.Out); } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs index 251f048fb6..f1deba7782 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public DefaultReverseArrow() { Divisor = 2; - MinimumBeatLength = 200; + MinimumBeatLength = 150; Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index c63ae35a3f..e1d1b088a2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void load(ISkinSource skinSource) { Divisor = 2; - MinimumBeatLength = 200; + MinimumBeatLength = 150; AutoSizeAxes = Axes.Both; From 8010410487c9e43434e4785af10f64156f4d81d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 19:09:57 +0900 Subject: [PATCH 22/37] Stop beat syncing (and close match stable's implementation of arrow animation) --- .../Skinning/Argon/ArgonReverseArrow.cs | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs index 1ce3a32900..d4084a76f1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,8 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -19,24 +16,20 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public partial class ArgonReverseArrow : BeatSyncedContainer + public partial class ArgonReverseArrow : CompositeDrawable { [Resolved] - private DrawableHitObject drawableRepeat { get; set; } = null!; + private DrawableHitObject drawableObject { get; set; } = null!; private Bindable accentColour = null!; private SpriteIcon icon = null!; - private Container main = null!; private Sprite side = null!; [BackgroundDependencyLoader] - private void load(TextureStore textures, DrawableHitObject hitObject) + private void load(TextureStore textures) { - Divisor = 2; - MinimumBeatLength = 150; - Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -76,22 +69,39 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon } }; - accentColour = hitObject.AccentColour.GetBoundCopy(); + accentColour = drawableObject.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true); + + drawableObject.ApplyCustomUpdateState += updateStateTransforms; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state) { - if (!drawableRepeat.Judged) + const float move_distance = -12; + const double move_out_duration = 35; + const double move_in_duration = 250; + const double total = 300; + + switch (state) { - main.ScaleTo(1.3f, timingPoint.BeatLength / 8, Easing.Out) - .Then() - .ScaleTo(1f, timingPoint.BeatLength / 2, Easing.Out); - side - .MoveToX(-12, timingPoint.BeatLength / 8, Easing.Out) - .Then() - .MoveToX(0, timingPoint.BeatLength / 2, Easing.Out); + case ArmedState.Idle: + main.ScaleTo(1.3f, move_out_duration, Easing.Out) + .Then() + .ScaleTo(1f, move_in_duration, Easing.Out) + .Loop(total - (move_in_duration + move_out_duration)); + side + .MoveToX(move_distance, move_out_duration, Easing.Out) + .Then() + .MoveToX(0, move_in_duration, Easing.Out) + .Loop(total - (move_in_duration + move_out_duration)); + break; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + } } } From e4ac8362ecd5329458356013e0c66eff310cf40d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 19:17:45 +0900 Subject: [PATCH 23/37] Update other implementations to use non-beat-sync logic --- .../Skinning/Default/DefaultReverseArrow.cs | 42 +++++++++---- .../Skinning/Legacy/LegacyReverseArrow.cs | 61 ++++++++++--------- 2 files changed, 62 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs index f1deba7782..a019a4767b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs @@ -2,33 +2,28 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public partial class DefaultReverseArrow : BeatSyncedContainer + public partial class DefaultReverseArrow : CompositeDrawable { [Resolved] - private DrawableHitObject drawableRepeat { get; set; } = null!; + private DrawableHitObject drawableObject { get; set; } = null!; public DefaultReverseArrow() { - Divisor = 2; - MinimumBeatLength = 150; - Anchor = Anchor.Centre; Origin = Anchor.Centre; Size = OsuHitObject.OBJECT_DIMENSIONS; - Child = new SpriteIcon + InternalChild = new SpriteIcon { RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, @@ -39,10 +34,33 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + [BackgroundDependencyLoader] + private void load() { - if (!drawableRepeat.Judged) - Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + drawableObject.ApplyCustomUpdateState += updateStateTransforms; + } + + private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state) + { + const double move_out_duration = 35; + const double move_in_duration = 250; + const double total = 300; + + switch (state) + { + case ArmedState.Idle: + InternalChild.ScaleTo(1.3f, move_out_duration, Easing.Out) + .Then() + .ScaleTo(1f, move_in_duration, Easing.Out) + .Loop(total - (move_in_duration + move_out_duration)); + break; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index e1d1b088a2..98ac770dd0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -3,11 +3,9 @@ using System.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Containers; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -16,13 +14,10 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public partial class LegacyReverseArrow : BeatSyncedContainer + public partial class LegacyReverseArrow : CompositeDrawable { [Resolved] - private DrawableHitObject drawableRepeat { get; set; } = null!; - - [Resolved(canBeNull: true)] - private DrawableHitObject? drawableHitObject { get; set; } + private DrawableHitObject drawableObject { get; set; } = null!; private Drawable proxy = null!; @@ -35,9 +30,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skinSource) { - Divisor = 2; - MinimumBeatLength = 150; - AutoSizeAxes = Axes.Both; string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName; @@ -46,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: OsuHitObject.OBJECT_DIMENSIONS) ?? Empty()); textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; + + drawableObject.ApplyCustomUpdateState += updateStateTransforms; } protected override void LoadComplete() @@ -54,23 +48,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy proxy = CreateProxy(); - if (drawableHitObject != null) + drawableObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(drawableObject); + + accentColour = drawableObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(c => { - drawableHitObject.HitObjectApplied += onHitObjectApplied; - onHitObjectApplied(drawableHitObject); - - accentColour = drawableHitObject.AccentColour.GetBoundCopy(); - accentColour.BindValueChanged(c => - { - arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White; - }, true); - } - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - if (!drawableRepeat.Judged) - Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White; + }, true); } private void onHitObjectApplied(DrawableHitObject drawableObject) @@ -82,11 +67,29 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .OverlayElementContainer.Add(proxy); } + private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state) + { + const double move_out_duration = 35; + const double move_in_duration = 250; + const double total = 300; + + switch (state) + { + case ArmedState.Idle: + InternalChild.ScaleTo(1.3f, move_out_duration, Easing.Out) + .Then() + .ScaleTo(1f, move_in_duration, Easing.Out) + .Loop(total - (move_in_duration + move_out_duration)); + break; + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (drawableHitObject != null) - drawableHitObject.HitObjectApplied -= onHitObjectApplied; + + drawableObject.HitObjectApplied -= onHitObjectApplied; + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From 1bee7bf353d286978b1b975c1d202c2730ce7b2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 19:22:24 +0900 Subject: [PATCH 24/37] Add note about rotation --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index 98ac770dd0..81781bc6b0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -76,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (state) { case ArmedState.Idle: + // TODO: rotate slightly if Version < 1 (aka UseNewLayout) InternalChild.ScaleTo(1.3f, move_out_duration, Easing.Out) .Then() .ScaleTo(1f, move_in_duration, Easing.Out) From f0070eecf10ce1fe7193e973eee20ae1720e9a12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 20:09:39 +0900 Subject: [PATCH 25/37] Add rotation support for very old skins --- .../Resources/old-skin/reversearrow.png | Bin 0 -> 4853 bytes .../Skinning/Legacy/LegacyReverseArrow.cs | 26 ++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/reversearrow.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/reversearrow.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/reversearrow.png new file mode 100644 index 0000000000000000000000000000000000000000..7ebdec37d356490d018f711ae525abb3250be3fd GIT binary patch literal 4853 zcmbtY_d6S2)K3TvF=AJY#9lQ+X=~T6O-qYfvDz9{dqfawR;=11ZH=O}s)``Awptzb zh+1DWsG^iN?|<+<&wHP9@A=`LAMUyLbI$pkn`myT&qxoW2LJ$!h6Xy86pi`c38tm2 zpVgg*D2h8k*EYb)*F7NE+20MIh4OWALl}BHd$?J;Iio_K4!EfT0IWHNI$G8tGusYy ze%89MU3(B%07OSunwYKw;u4JEO|ne7^3o{OyjsNNlUYlR^p^>T`RpD`i6LrpagI4i z?BFU(8YW&C2kA~?H9lfU~o91h-_28Sn)_H7H3 zH^njMCDtdJH*Vb6j_2`lI$R(1Pz(Qq5^7K1Blti;gpOh~lp|}_G`rK%)6-K^T8fF1MIbOHxpL8J z=j6|0P(SFR=waPVjWGGI4wMDKXp;-%U{*gt2zlVx1m)!X=7#f-%8QZ5^XI(}3*p-d5Ziwu*NI}%r9ohCT zMVdlYRaLgy+S)>f35o@2a1C`SdHH%xR>Q{t=6j&)1*rwmk!OFD&SFf9Zq^TEiapE3 z)Ee@n{5RirYco_Pakj3G$nh_eM-;H&fX875KO#ReY97C{`+5$ znF8HIUCfPVYsBQV^6|7@!2VI{a> z5H&%;Ziy)B;jZJws8IzZvLy(rWAJKJML71O)H3}HLM02qH*K_4G0KO zOioI2QJxQAOHN5aq0wlW)3%YKTYVpW<_9MxCSWGiEF_hs2)B#Hel6M#9q?_luJ#J;l+m^~h3^ukW9ivun(B!5#qEAJ#5-j4xzj5f0gPWOAy(!$j z-ES+#>9TKrj%RdDQ#}CN9J^B)cYpMrwG9iqSzTGVv?%oasWcrtCTF3Fak}THaC9dR z-31W8OPZ_4-+tbp50A`>i;K(C+W<3?*vrey{f}yMu?|<5`Oo``)xv*%9@LUgi2mMi zszm(#-#42HH&};W#KqjR(fZuntKYy2kD9cb4pRi~aq5ngrff$&w6L&Prhxj}zBG)_ z3t?kEI#T;>%ESb~8ll`;6BC>knAOK0o|4M1HsiM6D&?-a9q!^HILZiEIp-**AQMWb z7K`?YrgBS)knO-Y|tgIx_jTw)Rw(gq3ZHdZaOOi?sSWerbEU z@x1s+q&x}wHAEOxN{i-doR@Sb@G>UR`Q>H+tSU^~qh)wJ8Coy?ZiOnz7&y^7m2}NC zJ?2T)Z)j*RGT6}f{0V1?>8peyJB=CUO4uF{t8l~!P-)&mvCL&OIsH}9tza41207}w zs~>#kZ~i&k>qN4KIQaPNj-Q`LSm#+ofHw49LlzPpQKp1)y>SMn#z&Ya?VX{##d=r$ zG%i)nX!WygWY7{3h&Fq8@%FWT+U~1Q9maY4Zt>iX5@=fdBwg1XHYYY1(LNMIxjvy5 zB(+R>41)Ki!98NHFs~(6NCD$~o*Zrdm>akixM|J!_JUgRj5+zfG>xNV zeAR9>V@z|4C0Ry{_{K03Iw{S{gxFeW4=pD9ak`3hp z)KQX=cK*?{;?^CGt{m}h*}LwM@0@#QZ-ktMr^48x|fOo|qJ&Ag}{{-982Uo&mozGBTLPwikumgIb`fz4rx9{6E(;OE%7r zzoZe85g>j^WIOw|>hp9--_|zdBZ0uHts(6$1ZPkDqRpT4z!^(RdE2(pfdPqI z5hs3O4h|0WuXaZzUmI6O+}J8BYHqVi%&i6!A?e3<0(BeN!&{WGav9pcWX(x@}CMHI3i#Sts?1vp_Nc~k#f%9iZwqks$&9gca zo1lcWG`Mz-`V@L6=@sJvak{~6=u;By$0yVNxu8yc@N1*Qw$(~{28IurR9mllI9~#{;Nj2M`B`P@Ym+@4yt{EpeQ|y zvx$Xje36|m-!mGZdDPEgR``#bDu1Z}#$M-ppk=^*p; zjErve(-k(Ei2Tz~?UQvZIT=e{9j=A0>9`+k7uU%WIY1}W@B7c2IO{h5jNICj`w%X0 zX0Ow2_IlAy2IONwI8e|vpHQC+-kAxMLzT(s4dlrtkR4gk`Q}??+GIV_9ofZqchPn# z(S%pjciY~@`F5Pfd+hgHNC|n!7O97Gzu-uGdT-7K=CV&~d8y7`yzqS0v&IC_($cd1 zD^262{KYZ53_m;GE-vID-u%+?q{-;X9{J5X=l5d(f_fVT`tJ@Rroz9RoD4(g>HP8rBHpcI&rB-36`2k=dKx|D5efW#<6#`S5f?!~T=+R@3RbxuiS=L8}m zZB}wOX}*iFlD)ioFH~i76PG8{=4gOYk;Ga*L13!NTT(@D81j5AvaXEEg@WPuh28nK zC;_X37Ba^Q62gGZc(k|%b>gRWHgBalG8>)C%*$)hAyiD@NXTrmgBw+Y8|=rlriU!n z^X$(~BaT;cI&N!MFMF{j>0w*Y4y<+icJ})pkLJxTzOXelHHpNitQxUfxLhZ-4bmC+ zWLsB#D@i?&>MyOSsWAc@XK22(8Bp97+3(?U{E~WAe)-{n(3qgG0m0_pitT-?!71zR zU=AQol>|}ndtca6eMJ=DkB}~1JN|L>M=Et7(%>ZFa5nMy6x=o2a}?9f_&hOR@`V)rEB0Axu4 zu;u3ZwFIU`&y^`taKEihuu^wJW4Xf4O}s8D#nD%f)RqFEl=5owv>OVgxQh*>9RpE~ zGD08_*6z&LA~al<)CR1!j3@;Wdu@QQSO4b)OrhFM4;4sz*wM1OE((QG*3jq#^gL~= z%BzCvW)OSr?Ta2iq5>!>DV30r=YM8>L)S(m_-i^fMi+mztT4|-q>fb4w~-d`N<<~} zCqqL+4IHO|b~&T4A15s%O!yy0W&bige3P)b!PPtoFfY^9)g6+{>-f7_om}N_mvn3Q zQ*5H8jPbjG&Cxz{phKg@8m=Eyq^YgQ?vK}NLqBXvG z`^~=q*h9>P<(lz$5(7*WZxMsU=&@>ATYq<`qop+|B{6}ZFS6b>G#or}b|!^?((Oq_ zawd++(bI^HkB|S0jg5s{8kT;=gnI#qAD^v{P}t0BfyMAX#X-k9>1q*8rM;38QA`_D^AS+kwx4|%KPJaj?FUDnkd!odJkAmF->YqEfp2Jfr5+G%9s`dj zC}+b7c^aCTmH0HzJ|5HX_*QN*S;$6=W)f5gQAZ#U+Bl-)W1zAiriw;fu;1j)or1JI z*C@5Yx>}m4u=qMTaq-IV#g53!!I9&I@drdkB>=P`PQ1k>7^}iRt$ol6= zTCud!!U_yrO!!XDW*#rV5B9N2N<*@DP8mnyu+U$r%I_ugF?D1L1zs0MWY%T9!fplH zZlFaqm1u1Fsz$zkjT4=s?0;2WT;WlFkYt!CT5spu&T*mn#NK|+(wY9eNyATIhH(D_ zG`<$fRQPeE=w@L4ZKnAW@?k}BSQ4aBgoy5%ZuB^`^iA$*kU%1zQ9Z8OLLNz$^g)T5 zF#y9DB9Z8^*S4pg<#EVFO6HV)H2z0{_d9t|s+P?8uB@z#2dSV??6EF|hA9H)Djf)j z=n^BUq}4q>1yuj?($Z3~r(~Sa`~!bIK!!<(tbyi?lbSAU6w>H=7{vPNM=0=XI&pNg z*38&=c=6Ti*Uu5tY;gGK83Xt6zM}|ThcGLV*S5U~kV@3Ao!r z*}a=9D=YKvqA~hKyfZ0=xS7(%G4Q;&DleRaL#Zx!Z-F4qU8wt8tT%dWg9`nY%+elh zUS7WIEX-On+ZwoqbQg*45N~v->7hVTm!X%^>A&xDC@4Srz1;-z)Z?ji$G5w{VraNmlnNJO_;l(~ zf`W!iHUZ)SHxd@#R8*w7n#>c8*<}9aM)%V;F4cNsnFIqESh2XD z3LN)fM~a70uSc(l7?GLAk)`#w6YhV&q}?CRbK#8ONJT_q|F{GwrZCmKMKSE9OFN_b zSdPran$|n}KE$;18e55$fvMMb2E7-KYTlA=W1z2uv0>RFt{I@~KfeoUdMlD#KPi|1 zjzwarHwzu+PH#V=4K=E``8`!wlI}Y-+}^*iU@{j&;r^XJqOOFJH;0ehOwMTkWgB#t zI7FbTi(SkinConfiguration.LegacySetting.Version)?.Value <= 1; } protected override void LoadComplete() @@ -76,11 +80,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (state) { case ArmedState.Idle: - // TODO: rotate slightly if Version < 1 (aka UseNewLayout) - InternalChild.ScaleTo(1.3f, move_out_duration, Easing.Out) - .Then() - .ScaleTo(1f, move_in_duration, Easing.Out) - .Loop(total - (move_in_duration + move_out_duration)); + if (shouldRotate) + { + InternalChild.ScaleTo(1.3f, move_out_duration) + .RotateTo(5.625f) + .Then() + .ScaleTo(1f, move_in_duration) + .RotateTo(-5.625f, move_in_duration) + .Loop(total - (move_in_duration + move_out_duration)); + } + else + { + InternalChild.ScaleTo(1.3f) + .Then() + .ScaleTo(1f, move_in_duration) + .Loop(total - (move_in_duration + move_out_duration)); + } + break; } } From 94e49a34fdd1d827a500d0235800e40fff1cab41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 22:26:57 +0900 Subject: [PATCH 26/37] Adjust legacy reverse arrow implementation to match stable more closely --- .../Skinning/Legacy/LegacyReverseArrow.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index 09625c2aed..7e69c7a25b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -73,28 +73,26 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state) { - const double move_out_duration = 35; - const double move_in_duration = 250; - const double total = 300; + const double duration = 300; + const float rotation = 5.625f; switch (state) { case ArmedState.Idle: if (shouldRotate) { - InternalChild.ScaleTo(1.3f, move_out_duration) - .RotateTo(5.625f) + InternalChild.ScaleTo(1.3f) + .RotateTo(rotation) .Then() - .ScaleTo(1f, move_in_duration) - .RotateTo(-5.625f, move_in_duration) - .Loop(total - (move_in_duration + move_out_duration)); + .ScaleTo(1f, duration) + .RotateTo(-rotation, duration) + .Loop(); } else { - InternalChild.ScaleTo(1.3f) - .Then() - .ScaleTo(1f, move_in_duration) - .Loop(total - (move_in_duration + move_out_duration)); + InternalChild.ScaleTo(1.3f).Then() + .ScaleTo(1f, duration, Easing.Out) + .Loop(); } break; From 3aa51301e8b9f94c7088b59af796daf7d011e3fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2023 22:28:33 +0900 Subject: [PATCH 27/37] Add null checks in disposal flow --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs | 5 ++++- .../Skinning/Default/DefaultReverseArrow.cs | 5 ++++- .../Skinning/Legacy/LegacyReverseArrow.cs | 8 ++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs index d4084a76f1..160edb6f67 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -101,7 +102,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + + if (drawableObject.IsNotNull()) + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs index a019a4767b..b44f6571b9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultReverseArrow.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -60,7 +61,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + + if (drawableObject.IsNotNull()) + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index 7e69c7a25b..34bcf95e1d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -103,8 +104,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.Dispose(isDisposing); - drawableObject.HitObjectApplied -= onHitObjectApplied; - drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + if (drawableObject.IsNotNull()) + { + drawableObject.HitObjectApplied -= onHitObjectApplied; + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + } } } } From 16fcc4eaaa9818989a1ad39cb6921822ab35abc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 12:32:52 +0900 Subject: [PATCH 28/37] Fix incorrect anchor/origin causing rotation to look wrong on legacy skin arrows --- .../Skinning/Legacy/LegacyReverseArrow.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index 34bcf95e1d..25de6d2381 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -39,7 +39,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); - InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: OsuHitObject.OBJECT_DIMENSIONS) ?? Empty()); + InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: OsuHitObject.OBJECT_DIMENSIONS) ?? Empty()).With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + }); + textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; drawableObject.ApplyCustomUpdateState += updateStateTransforms; From b234a453da2319a4fc5101ecc5b5d41865232cce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 16:01:15 +0900 Subject: [PATCH 29/37] Fix legacy beatmap importer not always handling nested paths correctly Addresses https://github.com/ppy/osu/discussions/24989. --- osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs | 8 +++++++- osu.Game/Database/LegacyBeatmapImporter.cs | 5 ++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs b/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs index 0144c0bf97..5f722e381c 100644 --- a/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs +++ b/osu.Game.Tests/Database/LegacyBeatmapImporterTest.cs @@ -44,17 +44,23 @@ namespace osu.Game.Tests.Database createFile(subdirectory2, Path.Combine("beatmap5", "beatmap.osu")); createFile(subdirectory2, Path.Combine("beatmap6", "beatmap.osu")); + // songs subdirectory with random file + var subdirectory3 = songsStorage.GetStorageForDirectory("subdirectory3"); + createFile(subdirectory3, "silly readme.txt"); + createFile(subdirectory3, Path.Combine("beatmap7", "beatmap.osu")); + // empty songs subdirectory songsStorage.GetStorageForDirectory("subdirectory3"); string[] paths = importer.GetStableImportPaths(songsStorage).ToArray(); - Assert.That(paths.Length, Is.EqualTo(6)); + Assert.That(paths.Length, Is.EqualTo(7)); Assert.That(paths.Contains(songsStorage.GetFullPath("beatmap1"))); Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "beatmap2")))); Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "beatmap3")))); Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "sub-subdirectory", "beatmap4")))); Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory2", "beatmap5")))); Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory2", "beatmap6")))); + Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory3", "beatmap7")))); } static void createFile(Storage storage, string path) diff --git a/osu.Game/Database/LegacyBeatmapImporter.cs b/osu.Game/Database/LegacyBeatmapImporter.cs index 20add54949..a090698a68 100644 --- a/osu.Game/Database/LegacyBeatmapImporter.cs +++ b/osu.Game/Database/LegacyBeatmapImporter.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -34,9 +33,9 @@ namespace osu.Game.Database try { - if (!directoryStorage.GetFiles(string.Empty).ExcludeSystemFileNames().Any()) + if (!directoryStorage.GetFiles(string.Empty, "*.osu").Any()) { - // if a directory doesn't contain files, attempt looking for beatmaps inside of that directory. + // if a directory doesn't contain any beatmap files, look for further nested beatmap directories. // this is a special behaviour in stable for beatmaps only, see https://github.com/ppy/osu/issues/18615. foreach (string subDirectory in GetStableImportPaths(directoryStorage)) paths.Add(subDirectory); From 2cbec6dbdf115562d64d44f515ee9ad520e20416 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 17:11:52 +0900 Subject: [PATCH 30/37] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 58e18d3187..0ee922e53a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 8178cf3a8dce61729699668898bd61b6b80ee5b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 17:17:11 +0900 Subject: [PATCH 31/37] Tidy up background colour gradient logic This changes things visually a touch, but I think it feels better. And reads better. --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 6cf1daa102..f2f71ab64d 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -267,10 +267,12 @@ namespace osu.Game.Screens.Play.HUD protected override Color4 ColourAt(float position) { if (position <= 0.128f) - return Color4.White.Opacity(0.5f); + return Color4.White.Opacity(0.8f); - position -= 0.128f; - return Interpolation.ValueAt(Math.Clamp(position, 0f, 1f), Color4.White.Opacity(0.5f), Color4.Black.Opacity(0.5f), -0.75f, 1f, Easing.OutQuart); + return Interpolation.ValueAt(position, + Color4.White.Opacity(0.8f), + Color4.Black.Opacity(0.2f), + -0.5f, 1f, Easing.OutQuint); } } From e1445fcc65cf60800fd6f86785db051d821b5f64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 17:17:42 +0900 Subject: [PATCH 32/37] Adjust health display test scene's background colour to better visualise background fill --- osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 12a2611a76..06a7763711 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, + Colour = Color4.Gray, }, new ArgonHealthDisplay { From 4f9daa1c14eda97baf27a5ab547add008fe46171 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 17:31:50 +0900 Subject: [PATCH 33/37] Tidy up `ArgonHealthDisplay` code quality --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 183 +++++++++--------- 1 file changed, 88 insertions(+), 95 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index f2f71ab64d..6d16915576 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -25,55 +25,64 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonHealthDisplay : HealthDisplay, ISerialisableDrawable { + public bool UsesFixedAnchor { get; set; } + private const float curve_start = 280; private const float curve_end = 310; private const float curve_smoothness = 10; private const float bar_length = 350; - private const float bar_height = 32.5f; + private const float bar_verticality = 32.5f; private BarPath healthBar = null!; private BarPath missBar = null!; + private BackgroundPath background = null!; private SliderPath barPath = null!; - private static readonly Colour4 health_bar_colour = Colour4.White; + private static readonly Colour4 main_bar_colour = Colour4.White; + private static readonly Colour4 main_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.5f); - // the opacity isn't part of the design, it's only here to control glow intensity. - private static readonly Colour4 health_bar_glow_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.5f); - private static readonly Colour4 health_bar_flash_colour = Color4Extensions.FromHex("#7ED7FD").Opacity(0.8f); + private ScheduledDelegate? resetMissBarDelegate; - private static readonly Colour4 miss_bar_colour = Color4Extensions.FromHex("#FF9393"); - private static readonly Colour4 miss_bar_glow_colour = Color4Extensions.FromHex("#FD0000"); + private readonly List missBarVertices = new List(); + private readonly List healthBarVertices = new List(); - // the "flashed" glow colour is just a lightened version of the original one, not part of the design. - private static readonly Colour4 miss_bar_flash_colour = Color4Extensions.FromHex("#FF5D5D"); + private double missBarValue = 1; - public bool UsesFixedAnchor { get; set; } + public double MissBarValue + { + get => missBarValue; + set + { + if (missBarValue == value) + return; + + missBarValue = value; + updatePathVertices(); + } + } + + private double healthBarValue = 1; + + public double HealthBarValue + { + get => healthBarValue; + set + { + if (healthBarValue == value) + return; + + healthBarValue = value; + updatePathVertices(); + } + } [BackgroundDependencyLoader] private void load() { AutoSizeAxes = Axes.Both; - Vector2 diagonalDir = (new Vector2(curve_end, bar_height) - new Vector2(curve_start, 0)).Normalized(); - - // todo: SliderPath or parts of it should be moved away to a utility class as they're useful for making curved paths in general, as done here. - barPath = new SliderPath(new[] - { - new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(curve_start - curve_smoothness, 0), PathType.Bezier), - new PathControlPoint(new Vector2(curve_start, 0)), - new PathControlPoint(new Vector2(curve_start, 0) + diagonalDir * curve_smoothness, PathType.Linear), - new PathControlPoint(new Vector2(curve_end, bar_height) - diagonalDir * curve_smoothness, PathType.Bezier), - new PathControlPoint(new Vector2(curve_end, bar_height)), - new PathControlPoint(new Vector2(curve_end + curve_smoothness, bar_height), PathType.Linear), - new PathControlPoint(new Vector2(bar_length, bar_height)), - }); - - var vertices = new List(); - barPath.GetPathToProgress(vertices, 0.0, 1.0); - InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -83,7 +92,7 @@ namespace osu.Game.Screens.Play.HUD { new Circle { - Margin = new MarginPadding { Top = 10f - 3f / 2f, Left = -2f }, + Margin = new MarginPadding { Top = 8.5f, Left = -2 }, Size = new Vector2(50f, 3f), }, new Container @@ -91,10 +100,9 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new BackgroundPath + background = new BackgroundPath { PathRadius = 10f, - Vertices = vertices, }, missBar = new BarPath { @@ -103,25 +111,26 @@ namespace osu.Game.Screens.Play.HUD Blending = BlendingParameters.Additive, Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White), PathRadius = 40f, - GlowPortion = 0.9f, + // Kinda hacky, but results in correct positioning with increased path radius. Margin = new MarginPadding(-30f), - Vertices = vertices + GlowPortion = 0.9f, }, healthBar = new BarPath { AutoSizeAxes = Axes.None, RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, - BarColour = health_bar_colour, - GlowColour = health_bar_glow_colour, + BarColour = main_bar_colour, + GlowColour = main_bar_glow_colour, PathRadius = 10f, GlowPortion = 0.6f, - Vertices = vertices }, } } }, }; + + updatePath(); } protected override void LoadComplete() @@ -137,28 +146,38 @@ namespace osu.Game.Screens.Play.HUD if (resetMissBarDelegate == null) this.TransformTo(nameof(MissBarValue), v.NewValue, 300, Easing.OutQuint); }, true); - - updatePathVertices(); } protected override void Update() { base.Update(); - healthBar.Alpha = (float)Interpolation.DampContinuously(healthBar.Alpha, (float)(Current.Value > 0 ? 1 : 0), 40, Time.Elapsed); - missBar.Alpha = (float)Interpolation.DampContinuously(missBar.Alpha, (float)(MissBarValue > 0 ? 1 : 0), 40, Time.Elapsed); + healthBar.Alpha = (float)Interpolation.DampContinuously(healthBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); + missBar.Alpha = (float)Interpolation.DampContinuously(missBar.Alpha, MissBarValue > 0 ? 1 : 0, 40, Time.Elapsed); } - private ScheduledDelegate? resetMissBarDelegate; + protected override void Flash(JudgementResult result) + { + base.Flash(result); + + healthBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f)) + .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); + + if (resetMissBarDelegate == null) + { + missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 100, Easing.OutQuint) + .Then() + .TransformTo(nameof(BarPath.BarColour), main_bar_colour, 800, Easing.OutQuint); + + missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White) + .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 800, Easing.OutQuint); + } + } protected override void Miss(JudgementResult result) { base.Miss(result); - if (result.HealthAtJudgement == 0.0) - // health is already empty, nothing should be displayed here. - return; - if (resetMissBarDelegate != null) { resetMissBarDelegate.Cancel(); @@ -170,78 +189,52 @@ namespace osu.Game.Screens.Play.HUD this.Delay(500).Schedule(() => { this.TransformTo(nameof(MissBarValue), Current.Value, 300, Easing.OutQuint); - finishMissBarUsage(); }, out resetMissBarDelegate); - missBar.TransformTo(nameof(BarPath.BarColour), miss_bar_colour, 100, Easing.OutQuint) - .Then() - .TransformTo(nameof(BarPath.BarColour), miss_bar_flash_colour, 800, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() + .TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), miss_bar_glow_colour.Lighten(0.2f)) - .TransformTo(nameof(BarPath.GlowColour), miss_bar_glow_colour, 800, Easing.OutQuint); + missBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) + .TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint); } private void finishMissBarUsage() { if (Current.Value > 0) { - missBar.TransformTo(nameof(BarPath.BarColour), health_bar_colour, 300, Easing.In); - missBar.TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 300, Easing.In); + missBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In); + missBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In); } resetMissBarDelegate?.Cancel(); resetMissBarDelegate = null; } - protected override void Flash(JudgementResult result) + private void updatePath() { - base.Flash(result); + Vector2 diagonalDir = (new Vector2(curve_end, bar_verticality) - new Vector2(curve_start, 0)).Normalized(); - healthBar.TransformTo(nameof(BarPath.GlowColour), health_bar_flash_colour) - .TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 300, Easing.OutQuint); - - if (resetMissBarDelegate == null) + barPath = new SliderPath(new[] { - missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 100, Easing.OutQuint) - .Then() - .TransformTo(nameof(BarPath.BarColour), health_bar_colour, 800, Easing.OutQuint); + new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(curve_start - curve_smoothness, 0), PathType.Bezier), + new PathControlPoint(new Vector2(curve_start, 0)), + new PathControlPoint(new Vector2(curve_start, 0) + diagonalDir * curve_smoothness, PathType.Linear), + new PathControlPoint(new Vector2(curve_end, bar_verticality) - diagonalDir * curve_smoothness, PathType.Bezier), + new PathControlPoint(new Vector2(curve_end, bar_verticality)), + new PathControlPoint(new Vector2(curve_end + curve_smoothness, bar_verticality), PathType.Linear), + new PathControlPoint(new Vector2(bar_length, bar_verticality)), + }); - missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White) - .TransformTo(nameof(BarPath.GlowColour), health_bar_glow_colour, 800, Easing.OutQuint); - } - } + List vertices = new List(); + barPath.GetPathToProgress(vertices, 0.0, 1.0); - private double missBarValue = 1.0; - private readonly List missBarVertices = new List(); + background.Vertices = vertices; + healthBar.Vertices = vertices; + missBar.Vertices = vertices; - public double MissBarValue - { - get => missBarValue; - set - { - if (missBarValue == value) - return; - - missBarValue = value; - updatePathVertices(); - } - } - - private double healthBarValue = 1.0; - private readonly List healthBarVertices = new List(); - - public double HealthBarValue - { - get => healthBarValue; - set - { - if (healthBarValue == value) - return; - - healthBarValue = value; - updatePathVertices(); - } + updatePathVertices(); } private void updatePathVertices() From 32b2ac4974d13d98f9abf73f47b6c72a12cd65a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 17:41:10 +0900 Subject: [PATCH 34/37] Rename and refactor glow/miss bars to hopefully make more sense --- .../Screens/Play/HUD/ArgonHealthDisplay.cs | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 6d16915576..f7b90fe563 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -34,8 +34,13 @@ namespace osu.Game.Screens.Play.HUD private const float bar_length = 350; private const float bar_verticality = 32.5f; - private BarPath healthBar = null!; - private BarPath missBar = null!; + private BarPath mainBar = null!; + + /// + /// Used to show a glow at the end of the main bar, or red "damage" area when missing. + /// + private BarPath glowBar = null!; + private BackgroundPath background = null!; private SliderPath barPath = null!; @@ -48,17 +53,17 @@ namespace osu.Game.Screens.Play.HUD private readonly List missBarVertices = new List(); private readonly List healthBarVertices = new List(); - private double missBarValue = 1; + private double glowBarValue = 1; - public double MissBarValue + public double GlowBarValue { - get => missBarValue; + get => glowBarValue; set { - if (missBarValue == value) + if (glowBarValue == value) return; - missBarValue = value; + glowBarValue = value; updatePathVertices(); } } @@ -104,7 +109,7 @@ namespace osu.Game.Screens.Play.HUD { PathRadius = 10f, }, - missBar = new BarPath + glowBar = new BarPath { BarColour = Color4.White, GlowColour = OsuColour.Gray(0.5f), @@ -115,7 +120,7 @@ namespace osu.Game.Screens.Play.HUD Margin = new MarginPadding(-30f), GlowPortion = 0.9f, }, - healthBar = new BarPath + mainBar = new BarPath { AutoSizeAxes = Axes.None, RelativeSizeAxes = Axes.Both, @@ -139,12 +144,12 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(v => { - if (v.NewValue >= MissBarValue) - finishMissBarUsage(); + if (v.NewValue >= GlowBarValue) + finishMissDisplay(); this.TransformTo(nameof(HealthBarValue), v.NewValue, 300, Easing.OutQuint); if (resetMissBarDelegate == null) - this.TransformTo(nameof(MissBarValue), v.NewValue, 300, Easing.OutQuint); + this.TransformTo(nameof(GlowBarValue), v.NewValue, 300, Easing.OutQuint); }, true); } @@ -152,24 +157,24 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); - healthBar.Alpha = (float)Interpolation.DampContinuously(healthBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); - missBar.Alpha = (float)Interpolation.DampContinuously(missBar.Alpha, MissBarValue > 0 ? 1 : 0, 40, Time.Elapsed); + mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); + glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); } protected override void Flash(JudgementResult result) { base.Flash(result); - healthBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f)) - .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); + mainBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour.Opacity(0.8f)) + .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.OutQuint); if (resetMissBarDelegate == null) { - missBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 100, Easing.OutQuint) + glowBar.TransformTo(nameof(BarPath.BarColour), Colour4.White, 100, Easing.OutQuint) .Then() .TransformTo(nameof(BarPath.BarColour), main_bar_colour, 800, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White) + glowBar.TransformTo(nameof(BarPath.GlowColour), Colour4.White) .TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 800, Easing.OutQuint); } } @@ -184,27 +189,30 @@ namespace osu.Game.Screens.Play.HUD resetMissBarDelegate = null; } else - this.TransformTo(nameof(MissBarValue), HealthBarValue); + { + // Reset any ongoing animation immediately, else things get weird. + this.TransformTo(nameof(GlowBarValue), HealthBarValue); + } this.Delay(500).Schedule(() => { - this.TransformTo(nameof(MissBarValue), Current.Value, 300, Easing.OutQuint); - finishMissBarUsage(); + this.TransformTo(nameof(GlowBarValue), Current.Value, 300, Easing.OutQuint); + finishMissDisplay(); }, out resetMissBarDelegate); - missBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() + glowBar.TransformTo(nameof(BarPath.BarColour), new Colour4(255, 147, 147, 255), 100, Easing.OutQuint).Then() .TransformTo(nameof(BarPath.BarColour), new Colour4(255, 93, 93, 255), 800, Easing.OutQuint); - missBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) + glowBar.TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255).Lighten(0.2f)) .TransformTo(nameof(BarPath.GlowColour), new Colour4(253, 0, 0, 255), 800, Easing.OutQuint); } - private void finishMissBarUsage() + private void finishMissDisplay() { if (Current.Value > 0) { - missBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In); - missBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In); + glowBar.TransformTo(nameof(BarPath.BarColour), main_bar_colour, 300, Easing.In); + glowBar.TransformTo(nameof(BarPath.GlowColour), main_bar_glow_colour, 300, Easing.In); } resetMissBarDelegate?.Cancel(); @@ -231,8 +239,8 @@ namespace osu.Game.Screens.Play.HUD barPath.GetPathToProgress(vertices, 0.0, 1.0); background.Vertices = vertices; - healthBar.Vertices = vertices; - missBar.Vertices = vertices; + mainBar.Vertices = vertices; + glowBar.Vertices = vertices; updatePathVertices(); } @@ -240,7 +248,7 @@ namespace osu.Game.Screens.Play.HUD private void updatePathVertices() { barPath.GetPathToProgress(healthBarVertices, 0.0, healthBarValue); - barPath.GetPathToProgress(missBarVertices, healthBarValue, Math.Max(missBarValue, healthBarValue)); + barPath.GetPathToProgress(missBarVertices, healthBarValue, Math.Max(glowBarValue, healthBarValue)); if (healthBarVertices.Count == 0) healthBarVertices.Add(Vector2.Zero); @@ -248,11 +256,11 @@ namespace osu.Game.Screens.Play.HUD if (missBarVertices.Count == 0) missBarVertices.Add(Vector2.Zero); - missBar.Vertices = missBarVertices.Select(v => v - missBarVertices[0]).ToList(); - missBar.Position = missBarVertices[0]; + glowBar.Vertices = missBarVertices.Select(v => v - missBarVertices[0]).ToList(); + glowBar.Position = missBarVertices[0]; - healthBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList(); - healthBar.Position = healthBarVertices[0]; + mainBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList(); + mainBar.Position = healthBarVertices[0]; } private partial class BackgroundPath : SmoothPath From 4f3c433946173001e90f6768e9a06bc510c04f1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Oct 2023 17:41:52 +0900 Subject: [PATCH 35/37] Move vertex related constants local to method --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index f7b90fe563..62a4b958c2 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -27,13 +27,6 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - private const float curve_start = 280; - private const float curve_end = 310; - private const float curve_smoothness = 10; - - private const float bar_length = 350; - private const float bar_verticality = 32.5f; - private BarPath mainBar = null!; /// @@ -221,6 +214,13 @@ namespace osu.Game.Screens.Play.HUD private void updatePath() { + const float curve_start = 280; + const float curve_end = 310; + const float curve_smoothness = 10; + + const float bar_length = 350; + const float bar_verticality = 32.5f; + Vector2 diagonalDir = (new Vector2(curve_end, bar_verticality) - new Vector2(curve_start, 0)).Normalized(); barPath = new SliderPath(new[] From 48209872bfd73d347a7fa6f35958d77a659a6fbc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 4 Oct 2023 13:53:33 +0900 Subject: [PATCH 36/37] Fix spinner requirements being susceptible to FP precision --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index aca55c6bd9..e3dfe8e69a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -70,8 +70,11 @@ namespace osu.Game.Rulesets.Osu.Objects double secondsDuration = Duration / 1000; - SpinsRequired = (int)(minRps * secondsDuration); - MaximumBonusSpins = Math.Max(0, (int)(maxRps * secondsDuration) - SpinsRequired - bonus_spins_gap); + // Allow a 0.1ms floating point precision error in the calculation of the duration. + const double duration_error = 0.0001; + + SpinsRequired = (int)(minRps * secondsDuration + duration_error); + MaximumBonusSpins = Math.Max(0, (int)(maxRps * secondsDuration + duration_error) - SpinsRequired - bonus_spins_gap); } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) From 5a0faaa0b118e4935c05a78e00ec4214c69a6b5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Oct 2023 14:30:04 +0900 Subject: [PATCH 37/37] Fix `TestReplayExport` intermittent failure The previous fix was not working as it was checking the path for the prefix `_`, not the filename. See https://github.com/ppy/osu/runs/17415814653#r0s2 which clearly shows this. --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index feda251744..6a7fab86d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -173,7 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay string? filePath = null; // Files starting with _ are temporary, created by CreateFileSafely call. - AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !f.StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null); + AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null); AddAssert("filesize is non-zero", () => { using (var stream = LocalStorage.GetStream(filePath))