From b38de6e580e61cc125d3305fbccdd4bf7b4da40c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:51:54 +0300 Subject: [PATCH 01/33] Add skinning support for welcome sprite text --- osu.Game/Screens/Menu/IntroWelcome.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 639591cfef..f85dbe9abe 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -100,13 +100,13 @@ namespace osu.Game.Screens.Menu private class WelcomeIntroSequence : Container { - private Sprite welcomeText; + private Drawable welcomeText; private Container scaleContainer; public LogoVisualisation LogoVisualisation { get; private set; } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(TextureStore textures, IAPIProvider api) { Origin = Anchor.Centre; Anchor = Anchor.Centre; @@ -135,15 +135,17 @@ namespace osu.Game.Screens.Menu Size = new Vector2(480), Colour = Color4.Black }, - welcomeText = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = textures.Get(@"Intro/Welcome/welcome_text") - }, } }, }; + + if (api.LocalUser.Value.IsSupporter) + scaleContainer.Add(welcomeText = new SkinnableSprite(@"Intro/Welcome/welcome_text")); + else + scaleContainer.Add(welcomeText = new Sprite { Texture = textures.Get(@"Intro/Welcome/welcome_text") }); + + welcomeText.Anchor = Anchor.Centre; + welcomeText.Origin = Anchor.Centre; } protected override void LoadComplete() From b8ee786d774f2c504491ef41875f79b01c3636e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:52:12 +0300 Subject: [PATCH 02/33] Add skinning support for "welcome" sample --- osu.Game/Screens/Menu/IntroWelcome.cs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index f85dbe9abe..27eaa7eb3a 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -13,7 +13,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Online.API; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Screens.Menu @@ -23,8 +26,11 @@ namespace osu.Game.Screens.Menu protected override string BeatmapHash => "64e00d7022195959bfa3109d09c2e2276c8f12f486b91fcf6175583e973b48f2"; protected override string BeatmapFile => "welcome.osz"; private const double delay_step_two = 2142; - private Sample welcome; - private Sample pianoReverb; + + private SkinnableSound skinnableWelcome; + private ISample welcome; + + private ISample pianoReverb; protected override string SeeyaSampleName => "Intro/Welcome/seeya"; protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) @@ -40,10 +46,15 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, IAPIProvider api) { if (MenuVoice.Value) - welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); + { + if (api.LocalUser.Value.IsSupporter) + AddInternal(skinnableWelcome = new SkinnableSound(new SampleInfo(@"Intro/Welcome/welcome"))); + else + welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); + } pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); } @@ -65,7 +76,10 @@ namespace osu.Game.Screens.Menu AddInternal(intro); - welcome?.Play(); + if (skinnableWelcome != null) + skinnableWelcome.Play(); + else + welcome?.Play(); var reverbChannel = pianoReverb?.Play(); if (reverbChannel != null) From 2c1589e068dcc0af8d40ece788ed248fed782c8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 05:52:20 +0300 Subject: [PATCH 03/33] Add skinning support for "seeya" sample --- osu.Game/Screens/Menu/IntroScreen.cs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index afe75c5ef7..65f9dde1f9 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -13,14 +13,17 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; using Realms; @@ -55,7 +58,8 @@ namespace osu.Game.Screens.Menu private const int exit_delay = 3000; - private Sample seeya; + private SkinnableSound skinnableSeeya; + private ISample seeya; protected virtual string SeeyaSampleName => "Intro/seeya"; @@ -90,14 +94,18 @@ namespace osu.Game.Screens.Menu private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm) + private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm, IAPIProvider api) { // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - seeya = audio.Samples.Get(SeeyaSampleName); + + if (api.LocalUser.Value.IsSupporter) + AddInternal(skinnableSeeya = new SkinnableSound(new SampleInfo(SeeyaSampleName))); + else + seeya = audio.Samples.Get(SeeyaSampleName); // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) @@ -201,7 +209,14 @@ namespace osu.Game.Screens.Menu // we also handle the exit transition. if (MenuVoice.Value) { - seeya.Play(); + // ensure samples have been updated after resume before playing. + ScheduleAfterChildren(() => + { + if (skinnableSeeya != null) + skinnableSeeya.Play(); + else + seeya.Play(); + }); // if playing the outro voice, we have more time to have fun with the background track. // initially fade to almost silent then ramp out over the remaining time. From 8cbc8b944f1ec39622cabc2388db53bdacea3e9f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 17:25:13 +0300 Subject: [PATCH 04/33] Rescope scheduling and improve comment --- osu.Game/Screens/Menu/IntroScreen.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 65f9dde1f9..a6b54dd1f2 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -209,14 +209,15 @@ namespace osu.Game.Screens.Menu // we also handle the exit transition. if (MenuVoice.Value) { - // ensure samples have been updated after resume before playing. - ScheduleAfterChildren(() => + if (skinnableSeeya != null) { - if (skinnableSeeya != null) - skinnableSeeya.Play(); - else - seeya.Play(); - }); + // resuming a screen (i.e. calling OnResume) happens before the screen itself becomes alive, + // therefore skinnable samples may not be updated yet with the recently selected skin. + // schedule after children to ensure skinnable samples have processed skin changes before playing. + ScheduleAfterChildren(() => skinnableSeeya.Play()); + } + else + seeya.Play(); // if playing the outro voice, we have more time to have fun with the background track. // initially fade to almost silent then ramp out over the remaining time. From 32c7a023f8206ec6cde60e4d1d2dc9641eb06a66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 19:54:13 +0900 Subject: [PATCH 05/33] Make `OsuGame.ScreenChanged` `private` and non-`virtual` Just reducing complexity scope here. --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fb81e4fd14..7cd043083a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1140,7 +1140,7 @@ namespace osu.Game MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } - protected virtual void ScreenChanged(IScreen current, IScreen newScreen) + private void screenChanged(IScreen current, IScreen newScreen) { skinEditor.Reset(); @@ -1187,11 +1187,11 @@ namespace osu.Game } } - private void screenPushed(IScreen lastScreen, IScreen newScreen) => ScreenChanged(lastScreen, newScreen); + private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); private void screenExited(IScreen lastScreen, IScreen newScreen) { - ScreenChanged(lastScreen, newScreen); + screenChanged(lastScreen, newScreen); if (newScreen == null) Exit(); From ac55fea3c953a6c81326308200d4b905cd921c3b Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 11 Mar 2022 14:04:12 +0100 Subject: [PATCH 06/33] Confine the host cursor to area of 'everything' scaling container --- .../Graphics/Containers/ScalingContainer.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index d331b818a1..58d18e1b21 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -6,6 +6,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Layout; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens; @@ -66,7 +68,7 @@ namespace osu.Game.Graphics.Containers this.targetMode = targetMode; RelativeSizeAxes = Axes.Both; - InternalChild = sizableContainer = new AlwaysInputContainer + InternalChild = sizableContainer = new SizeableAlwaysInputContainer(targetMode == ScalingMode.Everything) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -209,13 +211,50 @@ namespace osu.Game.Graphics.Containers } } - private class AlwaysInputContainer : Container + private class SizeableAlwaysInputContainer : Container { + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private ISafeArea safeArea { get; set; } + + private readonly bool confineHostCursor; + private readonly LayoutValue cursorRectCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public AlwaysInputContainer() + /// + /// Container used for sizing/positioning purposes in . Always receives mouse input. + /// + /// Whether to confine the host cursor to the draw area of this container. + /// Cursor confinement will abide by the setting. + public SizeableAlwaysInputContainer(bool confineHostCursor) { RelativeSizeAxes = Axes.Both; + this.confineHostCursor = confineHostCursor; + + if (confineHostCursor) + AddLayout(cursorRectCache); + } + + protected override void Update() + { + base.Update(); + + if (confineHostCursor && !cursorRectCache.IsValid) + { + updateHostCursorConfineRect(); + cursorRectCache.Validate(); + } + } + + private void updateHostCursorConfineRect() + { + if (host.Window == null) return; + + bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero; + host.Window.CursorConfineRect = coversWholeScreen ? (RectangleF?)null : ToScreenSpace(DrawRectangle).AABBFloat; } } } From 9a1ade4f7907b695aeaa0d5ca19d18e63c4fa554 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Mar 2022 23:08:40 +0900 Subject: [PATCH 07/33] Refactor `SkinEditor` to support switching target screens without full reload --- osu.Game/OsuGame.cs | 4 +-- osu.Game/Skinning/Editor/SkinEditor.cs | 29 ++++++++++----- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 35 +++++++++++++++---- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7cd043083a..ae117d03d2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1142,8 +1142,6 @@ namespace osu.Game private void screenChanged(IScreen current, IScreen newScreen) { - skinEditor.Reset(); - switch (newScreen) { case IntroScreen intro: @@ -1185,6 +1183,8 @@ namespace osu.Game else BackButton.Hide(); } + + skinEditor.SetTarget((Screen)newScreen); } private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ae5cbc95f0..cd21507128 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Skinning.Editor protected override bool StartHidden => true; - private readonly Drawable targetScreen; + private Drawable targetScreen; private OsuTextFlowContainer headerText; @@ -42,11 +42,13 @@ namespace osu.Game.Skinning.Editor private bool hasBegunMutating; + private Container blueprintContainerContainer; + public SkinEditor(Drawable targetScreen) { - this.targetScreen = targetScreen; - RelativeSizeAxes = Axes.Both; + + UpdateTargetScreen(targetScreen); } [BackgroundDependencyLoader] @@ -113,13 +115,9 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.CentreLeft, RequestPlacement = placeComponent }, - new Container + blueprintContainerContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SkinBlueprintContainer(targetScreen), - } }, } } @@ -147,6 +145,21 @@ namespace osu.Game.Skinning.Editor }, true); } + public void UpdateTargetScreen(Drawable targetScreen) + { + this.targetScreen = targetScreen; + + Scheduler.AddOnce(loadBlueprintContainer); + + void loadBlueprintContainer() + { + blueprintContainerContainer.Children = new Drawable[] + { + new SkinBlueprintContainer(targetScreen), + }; + } + } + private void skinChanged() { headerText.Clear(); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 61c363b019..08dad2a146 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; @@ -114,15 +116,36 @@ namespace osu.Game.Skinning.Editor } /// - /// Exit any existing skin editor due to the game state changing. + /// Set a new target screen which will be used to find skinnable components. /// - public void Reset() + public void SetTarget(Screen screen) { - skinEditor?.Save(); - skinEditor?.Hide(); - skinEditor?.Expire(); + if (skinEditor == null) return; - skinEditor = null; + skinEditor.Save(); + + // AddOnce with paramter will ensure the newest target is loaded if there is any overlap. + Scheduler.AddOnce(setTarget, screen); + } + + private void setTarget(Screen target) + { + Debug.Assert(skinEditor != null); + + if (!target.IsLoaded) + { + Scheduler.AddOnce(setTarget, target); + return; + } + + if (skinEditor.State.Value == Visibility.Visible) + skinEditor.UpdateTargetScreen(target); + else + { + skinEditor.Hide(); + skinEditor.Expire(); + skinEditor = null; + } } } } From 3db42dd77254df799efa9334a2389efacf31e622 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 20:00:32 +0900 Subject: [PATCH 08/33] Allow skin editor to target different target containers for placement purposes --- osu.Game/Skinning/Editor/SkinEditor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index ae5cbc95f0..36b06f01d2 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -171,7 +171,12 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var targetContainer = getTarget(SkinnableTarget.MainHUDComponents); + var target = availableTargets.FirstOrDefault()?.Target; + + if (target == null) + return; + + var targetContainer = getTarget(target.Value); if (targetContainer == null) return; From d1a9b88fe7756e6925b26511459840009b3180c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:05:45 +0900 Subject: [PATCH 09/33] Fix typo in comment Co-authored-by: Salman Ahmed --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 08dad2a146..abd8272633 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Editor skinEditor.Save(); - // AddOnce with paramter will ensure the newest target is loaded if there is any overlap. + // AddOnce with parameter will ensure the newest target is loaded if there is any overlap. Scheduler.AddOnce(setTarget, screen); } From f95e753adbfb170317c68ba19155f16e1b3a10a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Mar 2022 16:10:06 +0900 Subject: [PATCH 10/33] Rename double-container variable name --- osu.Game/Skinning/Editor/SkinEditor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index cd21507128..829faab116 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -42,7 +42,7 @@ namespace osu.Game.Skinning.Editor private bool hasBegunMutating; - private Container blueprintContainerContainer; + private Container content; public SkinEditor(Drawable targetScreen) { @@ -115,7 +115,7 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.CentreLeft, RequestPlacement = placeComponent }, - blueprintContainerContainer = new Container + content = new Container { RelativeSizeAxes = Axes.Both, }, @@ -153,7 +153,7 @@ namespace osu.Game.Skinning.Editor void loadBlueprintContainer() { - blueprintContainerContainer.Children = new Drawable[] + content.Children = new Drawable[] { new SkinBlueprintContainer(targetScreen), }; From 720e1cd206154fc40f8889ef813f1e31d01d1e0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:35:23 +0300 Subject: [PATCH 11/33] Add failing test cases --- .../TestSceneDrawableStoryboardSprite.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 4011a6bc82..34e6d1996d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; @@ -52,6 +53,49 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("skinnable sprite has correct size", () => sprites.Any(s => Precision.AlmostEquals(s.ChildrenOfType().Single().Size, new Vector2(128, 128)))); } + [Test] + public void TestFlippedSprite() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("flip sprites", () => sprites.ForEach(s => + { + s.FlipH = true; + s.FlipV = true; + })); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + } + + [Test] + public void TestNegativeScale() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + } + + [Test] + public void TestNegativeScaleWithFlippedSprite() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1))); + AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight)); + AddStep("flip sprites", () => sprites.ForEach(s => + { + s.FlipH = true; + s.FlipV = true; + })); + AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft)); + } + private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) => new DrawableStoryboardSprite( new StoryboardSprite(lookupName, origin, initialPosition) From 0b8c89bfa81c88019f629c8941d542f15d06fc6d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 03:50:12 +0300 Subject: [PATCH 12/33] Fix drawable storyboard sprites not flipping origin on negative scale --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index eb877f3dff..ad4402dcc4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -79,7 +79,7 @@ namespace osu.Game.Storyboards.Drawables { var origin = base.Origin; - if (FlipH) + if ((FlipH || VectorScale.X < 0) && !(FlipH && VectorScale.X < 0)) { if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); @@ -87,7 +87,7 @@ namespace osu.Game.Storyboards.Drawables origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } - if (FlipV) + if ((FlipV || VectorScale.Y < 0) && !(FlipV && VectorScale.Y < 0)) { if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); From 9cf05080da2bb0125eaf3483326d1ab4fa0ab66c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 04:40:24 +0300 Subject: [PATCH 13/33] Simplify conditionals to one XOR operations with comments --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index ad4402dcc4..f3173497e8 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -79,7 +79,8 @@ namespace osu.Game.Storyboards.Drawables { var origin = base.Origin; - if ((FlipH || VectorScale.X < 0) && !(FlipH && VectorScale.X < 0)) + // Either flip horizontally or negative X scale, but not both. + if (FlipH ^ (VectorScale.X < 0)) { if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); @@ -87,7 +88,8 @@ namespace osu.Game.Storyboards.Drawables origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } - if ((FlipV || VectorScale.Y < 0) && !(FlipV && VectorScale.Y < 0)) + // Either flip vertically or negative Y scale, but not both. + if (FlipV ^ (VectorScale.Y < 0)) { if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); From 740a72e16d07b380177c331096780043ded35da1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 05:44:34 +0300 Subject: [PATCH 14/33] Share origin adjustment logic between storyboard sprite and animation --- .../Drawables/DrawableStoryboardAnimation.cs | 30 +------------ osu.Game/Storyboards/StoryboardExtensions.cs | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Storyboards/StoryboardExtensions.cs diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 81623a9307..28ac83a25c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; @@ -70,34 +69,9 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - protected override Vector2 DrawScale - => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; + protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; - public override Anchor Origin - { - get - { - var origin = base.Origin; - - if (FlipH) - { - if (origin.HasFlagFast(Anchor.x0)) - origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlagFast(Anchor.x2)) - origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - } - - if (FlipV) - { - if (origin.HasFlagFast(Anchor.y0)) - origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlagFast(Anchor.y2)) - origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - } - - return origin; - } - } + public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; diff --git a/osu.Game/Storyboards/StoryboardExtensions.cs b/osu.Game/Storyboards/StoryboardExtensions.cs new file mode 100644 index 0000000000..4e8251c9e7 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics; +using osuTK; + +namespace osu.Game.Storyboards +{ + public static class StoryboardExtensions + { + /// + /// Given an origin and a set of properties, adjust the origin to display the sprite/animation correctly. + /// + /// The current origin. + /// The vector scale. + /// Whether the element is flipped horizontally. + /// Whether the element is flipped vertically. + /// The adjusted origin. + public static Anchor AdjustOrigin(Anchor origin, Vector2 vectorScale, bool flipH, bool flipV) + { + // Either flip horizontally or negative X scale, but not both. + if (flipH ^ (vectorScale.X < 0)) + { + if (origin.HasFlagFast(Anchor.x0)) + origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + else if (origin.HasFlagFast(Anchor.x2)) + origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); + } + + // Either flip vertically or negative Y scale, but not both. + if (flipV ^ (vectorScale.Y < 0)) + { + if (origin.HasFlagFast(Anchor.y0)) + origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + else if (origin.HasFlagFast(Anchor.y2)) + origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); + } + + return origin; + } + } +} From c1697c7621358e02dccb710d79842696a983c669 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 14 Mar 2022 06:29:49 +0300 Subject: [PATCH 15/33] Update `DrawableStoryboardSprite` to use helper method --- .../Drawables/DrawableStoryboardAnimation.cs | 3 +- .../Drawables/DrawableStoryboardSprite.cs | 29 +------------------ 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 28ac83a25c..88cb5f40a1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -69,7 +69,8 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; + protected override Vector2 DrawScale + => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index f3173497e8..db10f13896 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -73,33 +72,7 @@ namespace osu.Game.Storyboards.Drawables protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale; - public override Anchor Origin - { - get - { - var origin = base.Origin; - - // Either flip horizontally or negative X scale, but not both. - if (FlipH ^ (VectorScale.X < 0)) - { - if (origin.HasFlagFast(Anchor.x0)) - origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlagFast(Anchor.x2)) - origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - } - - // Either flip vertically or negative Y scale, but not both. - if (FlipV ^ (VectorScale.Y < 0)) - { - if (origin.HasFlagFast(Anchor.y0)) - origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlagFast(Anchor.y2)) - origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - } - - return origin; - } - } + public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV); public override bool IsPresent => !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent; From 4ae6cba080d707402399b295f4a41b9503545991 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 13:49:38 +0900 Subject: [PATCH 16/33] Expose UseDevelopmentServer as virtual --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8b75de9718..602f84b0e1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -69,7 +69,7 @@ namespace osu.Game /// private const double global_track_volume_adjust = 0.8; - public bool UseDevelopmentServer { get; } + public virtual bool UseDevelopmentServer { get; } public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); From 21beb8774d5d3d95bbcd19e524e571e181512048 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 14 Mar 2022 13:54:54 +0900 Subject: [PATCH 17/33] Change to lambda method --- osu.Game/OsuGameBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 602f84b0e1..5468db348e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -69,7 +69,7 @@ namespace osu.Game /// private const double global_track_volume_adjust = 0.8; - public virtual bool UseDevelopmentServer { get; } + public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -177,7 +177,6 @@ namespace osu.Game public OsuGameBase() { - UseDevelopmentServer = DebugUtils.IsDebugBuild; Name = @"osu!"; } From 8676a2587c12c2a200d6122d53736a611401e1a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 15:45:43 +0900 Subject: [PATCH 18/33] Add the ability for `HitObject`s to specify auxiliary samples --- osu.Game/Rulesets/Objects/HitObject.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c590cc302f..57b897e5b5 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; @@ -67,6 +68,12 @@ namespace osu.Game.Rulesets.Objects } } + /// + /// Any samples which may be used by this hit object that are non-standard. + /// This is used only to preload these samples ahead of time. + /// + public virtual IList AuxiliarySamples => ImmutableList.Empty; + public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT; public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT; From 90e34d7686b9a9762ba153ca841c5ad685497f3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 15:45:57 +0900 Subject: [PATCH 19/33] Move slider slide samples to auxiliary specification --- .../Objects/Drawables/DrawableSlider.cs | 22 +++++-------------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 17 ++++++++++++++ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3acec4498d..1447f131c6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -2,23 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; -using osuTK; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; -using osuTK.Graphics; using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -126,18 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); - - var slidingSamples = new List(); - - var normalSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); - if (normalSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide")); - - var whistleSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); - if (whistleSample != null) - slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle")); - - slidingSample.Samples = slidingSamples.ToArray(); + slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); } public override void StopAllSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5c1c3fd253..601311694d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -29,6 +29,23 @@ namespace osu.Game.Rulesets.Osu.Objects set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } + public override IList AuxiliarySamples => CreateSlidingSamples(); + + public IList CreateSlidingSamples() + { + var slidingSamples = new List(); + + var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + if (normalSample != null) + slidingSamples.Add(normalSample.With("sliderslide")); + + var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); + if (whistleSample != null) + slidingSamples.Add(whistleSample.With("sliderwhistle")); + + return slidingSamples; + } + private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); From be9920218805cc3280fb1e16d3816774305ca069 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:04 +0900 Subject: [PATCH 20/33] Move spinner spin samples to auxiliary specification --- .../Objects/Drawables/DrawableSpinner.cs | 11 ++--------- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c6db02ee02..a904658a4c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -121,15 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - var firstSample = HitObject.Samples.FirstOrDefault(); - - if (firstSample != null) - { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin"); - - spinningSample.Samples = new ISampleInfo[] { clone }; - spinningSample.Frequency.Value = spinning_sample_initial_frequency; - } + spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); + spinningSample.Frequency.Value = spinning_sample_initial_frequency; } private void updateSpinningSample(ValueChangedEvent tracking) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1eddfb7fef..ddee4d3ebd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,7 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -73,5 +77,20 @@ namespace osu.Game.Rulesets.Osu.Objects public override Judgement CreateJudgement() => new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public override IList AuxiliarySamples => CreateSpinningSamples(); + + public HitSampleInfo[] CreateSpinningSamples() + { + var referenceSample = Samples.FirstOrDefault(); + + if (referenceSample == null) + return Array.Empty(); + + return new[] + { + SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin") + }; + } } } From 6d6f73e0168d6c7d5c447e0b32c6d8566d5b4761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:26 +0900 Subject: [PATCH 21/33] Add overrides in `DrawableSliderTail` to explain/warn that this class never plays its own samples --- .../Objects/Drawables/DrawableSliderTail.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index ec1387eb54..64964ed396 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -74,6 +74,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); } + protected override void LoadSamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + + public override void PlaySamples() + { + // Tail models don't actually get samples, as the playback is handled by DrawableSlider. + // This override is only here for visibility in explaining this weird flow. + } + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); From 45233932089ea26d9e329c47f1893137d2d4bcaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:08:57 +0900 Subject: [PATCH 22/33] Remove `IsLayered` from `LegacyHitSampleInfo` comparison The equality of samples is generally used to compare the sample equality, not its full properties. For instance, we don't compare `Volume` in the base implementation. Having `IsLayered` here breaks actual usages of equality, ie. for pooling purposes. --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index b091803406..47fe9032f0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -500,7 +500,7 @@ namespace osu.Game.Rulesets.Objects.Legacy => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) - => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered; + => base.Equals(other) && CustomSampleBank == other.CustomSampleBank; public override bool Equals(object? obj) => obj is LegacyHitSampleInfo other && Equals(other); From 1b8c632b878b6d5c30a7a5693dc5834d0aa987b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:17:14 +0900 Subject: [PATCH 23/33] Add `TailSamples` to auxiliary samples list --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 601311694d..776165cfb4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public override IList AuxiliarySamples => CreateSlidingSamples(); + public override IList AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray(); public IList CreateSlidingSamples() { From 39d95aa8cf412b1a5ef66e1cd1d05541fef4f3bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:19:23 +0900 Subject: [PATCH 24/33] Add automatic preloading of sample pools at a `Playfield` level --- osu.Game/Rulesets/UI/Playfield.cs | 73 +++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 30e71dde1c..cd8f99db8b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -3,22 +3,22 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; -using System.Diagnostics; namespace osu.Game.Rulesets.UI { @@ -264,10 +264,25 @@ namespace osu.Game.Rulesets.UI var entry = CreateLifetimeEntry(hitObject); lifetimeEntryMap[entry.HitObject] = entry; + preloadSamples(hitObject); + HitObjectContainer.Add(entry); OnHitObjectAdded(entry.HitObject); } + private void preloadSamples(HitObject hitObject) + { + // prepare sample pools ahead of time so we're not initialising at runtime. + foreach (var sample in hitObject.Samples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var sample in hitObject.AuxiliarySamples) + prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample)); + + foreach (var nestedObject in hitObject.NestedHitObjects) + preloadSamples(nestedObject); + } + /// /// Removes a for a pooled from this . /// @@ -330,22 +345,7 @@ namespace osu.Game.Rulesets.UI DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent) { - var lookupType = hitObject.GetType(); - - IDrawablePool pool; - - // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. - if (!pools.TryGetValue(lookupType, out pool)) - { - foreach (var (t, p) in pools) - { - if (!t.IsInstanceOfType(hitObject)) - continue; - - pools[lookupType] = pool = p; - break; - } - } + var pool = prepareDrawableHitObjectPool(hitObject); return (DrawableHitObject)pool?.Get(d => { @@ -372,14 +372,39 @@ namespace osu.Game.Rulesets.UI }); } + private IDrawablePool prepareDrawableHitObjectPool(HitObject hitObject) + { + var lookupType = hitObject.GetType(); + + IDrawablePool pool; + + // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists. + if (!pools.TryGetValue(lookupType, out pool)) + { + foreach (var (t, p) in pools) + { + if (!t.IsInstanceOfType(hitObject)) + continue; + + pools[lookupType] = pool = p; + break; + } + } + + return pool; + } + private readonly Dictionary> samplePools = new Dictionary>(); - public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) - { - if (!samplePools.TryGetValue(sampleInfo, out var existingPool)) - AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1)); + public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) => prepareSamplePool(sampleInfo).Get(); - return existingPool.Get(); + private DrawablePool prepareSamplePool(ISampleInfo sampleInfo) + { + if (samplePools.TryGetValue(sampleInfo, out var pool)) return pool; + + AddInternal(samplePools[sampleInfo] = pool = new DrawableSamplePool(sampleInfo, 1)); + + return pool; } private class DrawableSamplePool : DrawablePool From 3c5fda5f23fce4a384cf286b4cab2ff180cfaa33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 17:24:31 +0900 Subject: [PATCH 25/33] Add early exist if the target screen is no longer current --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index abd8272633..73af2591c8 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -132,6 +132,9 @@ namespace osu.Game.Skinning.Editor { Debug.Assert(skinEditor != null); + if (!target.IsCurrentScreen()) + return; + if (!target.IsLoaded) { Scheduler.AddOnce(setTarget, target); From 16ee6b5fc7700b2a46359fb6b814e9c56eafaa53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 18:12:04 +0900 Subject: [PATCH 26/33] Remove `IsLayered` from `GetHasCode` implementation --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 47fe9032f0..8dc037c7c8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public override bool Equals(object? obj) => obj is LegacyHitSampleInfo other && Equals(other); - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered); + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank); } private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable From b31ff679fb1af632f8b40d980a3d3528a08e43a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:16:19 +0900 Subject: [PATCH 27/33] Provide correct `HitResult` type for random judgements in `TestSceneHitErrorMeter` --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index c1260f0231..a93ab6810d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -48,7 +48,11 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create display", () => recreateDisplay(new OsuHitWindows(), 5)); - AddRepeatStep("New random judgement", () => newJudgement(), 40); + AddRepeatStep("New random judgement", () => + { + double offset = RNG.Next(-150, 150); + newJudgement(offset, drawableRuleset.HitWindows.ResultFor(offset)); + }, 400); AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20); From 6eed2c35a4cb79877ced63626745d8768fb196a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:16:38 +0900 Subject: [PATCH 28/33] Adjust visual appearance of `BarHitErrorMeter` for easier reading --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 157 ++++++++++-------- 1 file changed, 89 insertions(+), 68 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 7903e54960..2f9206e0fb 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { @@ -21,14 +20,15 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { private const int arrow_move_duration = 400; - private const int judgement_line_width = 6; + private const int judgement_line_width = 10; + private const int judgement_line_height = 3; + + private const int centre_marker_size = 6; private const int bar_height = 200; private const int bar_width = 2; - private const int spacing = 2; - private const float chevron_size = 8; private SpriteIcon arrow; @@ -50,54 +50,25 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { - InternalChild = new FillFlowContainer + var hitWindows = HitWindows.GetAllAvailableWindows().ToArray(); + + InternalChild = new Container { AutoSizeAxes = Axes.X, Height = bar_height, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), Margin = new MarginPadding(2), Children = new Drawable[] { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = chevron_size, - RelativeSizeAxes = Axes.Y, - Child = arrow = new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.5f, - Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(chevron_size), - } - }, colourBars = new Container { - Width = bar_width, - RelativeSizeAxes = Axes.Y, + Name = "colour axis", + X = chevron_size, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Width = judgement_line_width, + RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - colourBarsEarly = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Scale = new Vector2(1, -1), - }, - colourBarsLate = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - }, iconEarly = new SpriteIcon { Y = -10, @@ -113,20 +84,72 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, - } + }, + colourBarsEarly = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Scale = new Vector2(1, -1), + }, + colourBarsLate = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + }, + judgementsContainer = new Container + { + Name = "judgements", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = judgement_line_width, + }, + new Circle + { + Name = "middle marker behind", + Colour = GetColourForHitResult(hitWindows.Last().result), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + }, + new Circle + { + Name = "middle marker in front", + Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + Scale = new Vector2(0.5f), + }, } }, - judgementsContainer = new Container + new Container { + Name = "average chevron", Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Width = judgement_line_width, + Width = chevron_size, RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.5f, + Icon = FontAwesome.Solid.ChevronRight, + Size = new Vector2(chevron_size), + } }, } }; - createColourBars(); + createColourBars(hitWindows); } protected override void LoadComplete() @@ -149,10 +172,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate.Rotation = -Rotation; } - private void createColourBars() + private void createColourBars((HitResult result, double length)[] windows) { - var windows = HitWindows.GetAllAvailableWindows().ToArray(); - // max to avoid div-by-zero. maxHitWindow = Math.Max(1, windows.First().length); @@ -166,17 +187,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters colourBarsLate.Add(createColourBar(result, hitWindow, i == 0)); } - // a little nub to mark the centre point. - var centre = createColourBar(windows.Last().result, 0.01f); - centre.Anchor = centre.Origin = Anchor.CentreLeft; - centre.Width = 2.5f; - colourBars.Add(centre); - - Drawable createColourBar(HitResult result, float height, bool first = false) + Drawable createColourBar(HitResult result, float height, bool requireGradient = false) { var colour = GetColourForHitResult(result); - if (first) + if (requireGradient) { // the first bar needs gradient rendering. const float gradient_start = 0.8f; @@ -243,7 +258,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Origin = Anchor.CentreLeft, + Colour = GetColourForHitResult(judgement.Type), }); arrow.MoveToY( @@ -255,34 +270,40 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters internal class JudgementLine : CompositeDrawable { - private const int judgement_fade_duration = 5000; - public JudgementLine() { RelativeSizeAxes = Axes.X; RelativePositionAxes = Axes.Y; - Height = 3; + Height = judgement_line_height; - InternalChild = new CircularContainer + Blending = BlendingParameters.Additive; + + Origin = Anchor.Centre; + Anchor = Anchor.TopCentre; + + InternalChild = new Circle { - Masking = true, RelativeSizeAxes = Axes.Both, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - } }; } protected override void LoadComplete() { + const int judgement_fade_in_duration = 100; + const int judgement_fade_out_duration = 5000; + base.LoadComplete(); + Alpha = 0; Width = 0; - this.ResizeWidthTo(1, 200, Easing.OutElasticHalf); - this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration).Expire(); + this + .FadeTo(0.8f, judgement_fade_in_duration, Easing.OutQuint) + .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint) + .Then() + .FadeOut(judgement_fade_out_duration, Easing.In) + .ResizeWidthTo(0, judgement_fade_out_duration, Easing.In) + .Expire(); } } From e91b3ae5f19fa70fa4062be6aef4e726f12ba10c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:18:47 +0900 Subject: [PATCH 29/33] Move constants closer to usages --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 2f9206e0fb..521d8c01b1 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -18,19 +18,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { - private const int arrow_move_duration = 400; - private const int judgement_line_width = 10; private const int judgement_line_height = 3; - private const int centre_marker_size = 6; - - private const int bar_height = 200; - - private const int bar_width = 2; - - private const float chevron_size = 8; - private SpriteIcon arrow; private SpriteIcon iconEarly; private SpriteIcon iconLate; @@ -50,6 +40,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { + const int centre_marker_size = 6; + const int bar_height = 200; + const int bar_width = 2; + const float chevron_size = 8; + const float icon_size = 14; + var hitWindows = HitWindows.GetAllAvailableWindows().ToArray(); InternalChild = new Container @@ -72,7 +68,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconEarly = new SpriteIcon { Y = -10, - Size = new Vector2(10), + Size = new Vector2(icon_size), Icon = FontAwesome.Solid.ShippingFast, Anchor = Anchor.TopCentre, Origin = Anchor.Centre, @@ -80,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters iconLate = new SpriteIcon { Y = 10, - Size = new Vector2(10), + Size = new Vector2(icon_size), Icon = FontAwesome.Solid.Bicycle, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, @@ -235,6 +231,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void OnNewJudgement(JudgementResult judgement) { + const int arrow_move_duration = 400; + if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; From 163cd48bf63ec43c3b9677340976544b109eca2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 19:27:53 +0900 Subject: [PATCH 30/33] Further metrics tweaking --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 521d8c01b1..542731cf93 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -18,8 +18,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { - private const int judgement_line_width = 10; - private const int judgement_line_height = 3; + private const int judgement_line_width = 14; + private const int judgement_line_height = 4; private SpriteIcon arrow; private SpriteIcon iconEarly; @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters [BackgroundDependencyLoader] private void load() { - const int centre_marker_size = 6; + const int centre_marker_size = 8; const int bar_height = 200; const int bar_width = 2; const float chevron_size = 8; @@ -98,6 +98,14 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Y, Height = 0.5f, }, + new Circle + { + Name = "middle marker behind", + Colour = GetColourForHitResult(hitWindows.Last().result), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(centre_marker_size), + }, judgementsContainer = new Container { Name = "judgements", @@ -107,17 +115,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = judgement_line_width, }, new Circle - { - Name = "middle marker behind", - Colour = GetColourForHitResult(hitWindows.Last().result), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(centre_marker_size), - }, - new Circle { Name = "middle marker in front", - Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.5f), + Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f), Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(centre_marker_size), @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected override void OnNewJudgement(JudgementResult judgement) { - const int arrow_move_duration = 400; + const int arrow_move_duration = 800; if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) return; @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters arrow.MoveToY( getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) - , arrow_move_duration, Easing.Out); + , arrow_move_duration, Easing.OutQuint); } private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1); @@ -296,11 +296,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = 0; this - .FadeTo(0.8f, judgement_fade_in_duration, Easing.OutQuint) + .FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint) .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint) .Then() - .FadeOut(judgement_fade_out_duration, Easing.In) - .ResizeWidthTo(0, judgement_fade_out_duration, Easing.In) + .FadeOut(judgement_fade_out_duration) + .ResizeWidthTo(0, judgement_fade_out_duration, Easing.InQuint) .Expire(); } } From 8fdf2b13ac2d83b206d88940e7582ae80c91a645 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Mar 2022 22:23:50 +0900 Subject: [PATCH 31/33] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 839f7882bf..1b5461959a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c106c373c4..5e194e2aca 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 86cf1b229c..23101c5af6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 4ff6879b85baed3b42560431b7c4962b7804c0d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 11:30:57 +0900 Subject: [PATCH 32/33] Fix incorrect copied room end dates --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index fb8647284f..a2d3b7f4fc 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -334,6 +334,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. r.RoomID.Value = null; + // Null out dates because end date is not supported client-side and the settings overlay will populate a duration. + r.EndDate.Value = null; + r.Duration.Value = null; + Open(r); joiningRoomOperation?.Dispose(); From 523f668c8cae5211cab71fcc711d34aac70fdd94 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 15 Mar 2022 12:37:39 +0900 Subject: [PATCH 33/33] Remove unnecessary ctor argument --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Difficulty/CatchPerformanceCalculator.cs | 4 ++-- .../Difficulty/ManiaPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 3a7efb6263..80b9436b2c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Catch public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); - public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 6fefb2e5bb..b30b85be2d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private int tinyTicksMissed; private int misses; - public CatchPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public CatchPerformanceCalculator() + : base(new CatchRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 1eaf45e54a..b347cc9ae2 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public ManiaPerformanceCalculator() + : base(new ManiaRuleset()) { } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d04ef69dd2..bd6a67bf67 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); - public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 5644b2009d..a93a1641a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double effectiveMissCount; - public OsuPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public OsuPerformanceCalculator() + : base(new OsuRuleset()) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 92b90339f9..2fdf42fca1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 9e73390fad..a8122551ff 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset) - : base(ruleset) + public TaikoPerformanceCalculator() + : base(new TaikoRuleset()) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 8934b64ca5..615fbf093f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(this); + public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(); public int LegacyID => 1;