From f561120295d392678a19d5a8a578da272a082bc0 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 7 Jan 2023 23:40:02 +0100 Subject: [PATCH 001/214] Remove info labels, since they are no longer present in this component in thew new design --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs new file mode 100644 index 000000000000..4d1a2133fd87 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -0,0 +1,356 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using System.Threading; +using osuTK; +using osuTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Effects; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Select +{ + public partial class BeatmapInfoWedgeV2 : VisibilityContainer + { + public const float BORDER_THICKNESS = 2.5f; + private const float shear_width = 36.75f; + + private const float transition_duration = 250; + + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); + + [Resolved] + private IBindable ruleset { get; set; } + + protected Container DisplayedContent { get; private set; } + + protected WedgeInfoText Info { get; private set; } + + public BeatmapInfoWedgeV2() + { + Shear = wedged_container_shear; + Masking = true; + BorderColour = new Color4(221, 255, 255, 255); + BorderThickness = BORDER_THICKNESS; + Alpha = 0; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = new Color4(130, 204, 255, 150), + Radius = 20, + Roundness = 15, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + ruleset.BindValueChanged(_ => updateDisplay()); + } + + private const double animation_duration = 800; + + protected override void PopIn() + { + this.MoveToX(0, animation_duration, Easing.OutQuint); + this.RotateTo(0, animation_duration, Easing.OutQuint); + this.FadeIn(transition_duration); + } + + protected override void PopOut() + { + this.MoveToX(-100, animation_duration, Easing.In); + this.RotateTo(10, animation_duration, Easing.In); + this.FadeOut(transition_duration * 2, Easing.In); + } + + private WorkingBeatmap beatmap; + + public WorkingBeatmap Beatmap + { + get => beatmap; + set + { + if (beatmap == value) return; + + beatmap = value; + + updateDisplay(); + } + } + + public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback + + private Container loadingInfo; + + private void updateDisplay() + { + Scheduler.AddOnce(perform); + + void perform() + { + void removeOldInfo() + { + State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; + + DisplayedContent?.FadeOut(transition_duration); + DisplayedContent?.Expire(); + DisplayedContent = null; + } + + if (beatmap == null) + { + removeOldInfo(); + return; + } + + LoadComponentAsync(loadingInfo = new Container + { + RelativeSizeAxes = Axes.Both, + Shear = -Shear, + Depth = DisplayedContent?.Depth + 1 ?? 0, + Children = new Drawable[] + { + new BeatmapInfoWedgeBackground(beatmap), + Info = new WedgeInfoText(beatmap), + } + }, loaded => + { + // ensure we are the most recent loaded wedge. + if (loaded != loadingInfo) return; + + removeOldInfo(); + Add(DisplayedContent = loaded); + }); + } + } + + public partial class WedgeInfoText : Container + { + public OsuSpriteText VersionLabel { get; private set; } + public OsuSpriteText TitleLabel { get; private set; } + public OsuSpriteText ArtistLabel { get; private set; } + public FillFlowContainer MapperContainer { get; private set; } + + private Container difficultyColourBar; + private StarRatingDisplay starRatingDisplay; + + private ILocalisedBindableString titleBinding; + private ILocalisedBindableString artistBinding; + + private readonly WorkingBeatmap working; + + [Resolved] + private IBindable> mods { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + private ModSettingChangeTracker settingChangeTracker; + + public WedgeInfoText(WorkingBeatmap working) + { + this.working = working; + } + + private CancellationTokenSource cancellationSource; + private IBindable starDifficulty; + + [BackgroundDependencyLoader] + private void load(LocalisationManager localisation) + { + var beatmapInfo = working.BeatmapInfo; + var metadata = beatmapInfo.Metadata; + + RelativeSizeAxes = Axes.Both; + + titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); + artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); + + const float top_height = 0.7f; + + Children = new Drawable[] + { + difficultyColourBar = new Container + { + RelativeSizeAxes = Axes.Y, + Width = 20f, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Width = top_height, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Alpha = 0.5f, + X = top_height, + Width = 1 - top_height, + } + } + }, + new FillFlowContainer + { + Name = "Topleft-aligned metadata", + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 10, Left = 25, Right = shear_width * 2.5f }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + VersionLabel = new OsuSpriteText + { + Text = beatmapInfo.DifficultyName, + Font = OsuFont.GetFont(size: 24, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, + }, + } + }, + new FillFlowContainer + { + Name = "Topright-aligned metadata", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, + AutoSizeAxes = Axes.Both, + Shear = wedged_container_shear, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + starRatingDisplay = new StarRatingDisplay(default, animated: true) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + Alpha = 0f, + }, + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapInfo.Status, + Alpha = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? 0 : 1 + } + } + }, + new FillFlowContainer + { + Name = "Centre-aligned metadata", + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft, + Y = -7, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Left = 25, Right = shear_width }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + TitleLabel = new OsuSpriteText + { + Current = { BindTarget = titleBinding }, + Font = OsuFont.GetFont(size: 28, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, + }, + ArtistLabel = new OsuSpriteText + { + Current = { BindTarget = artistBinding }, + Font = OsuFont.GetFont(size: 17, italics: true), + RelativeSizeAxes = Axes.X, + Truncate = true, + }, + MapperContainer = new FillFlowContainer + { + Margin = new MarginPadding { Top = 10 }, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Child = getMapper(metadata), + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + starRatingDisplay.DisplayedStars.BindValueChanged(s => + { + difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); + }, true); + + starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => + { + starRatingDisplay.Current.Value = s.NewValue ?? default; + + // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); + + starRatingDisplay.FadeIn(transition_duration); + }); + + mods.BindValueChanged(m => + { + settingChangeTracker?.Dispose(); + + settingChangeTracker = new ModSettingChangeTracker(m.NewValue); + }, true); + } + + private Drawable getMapper(BeatmapMetadata metadata) + { + if (string.IsNullOrEmpty(metadata.Author.Username)) + return Empty(); + + return new LinkFlowContainer(s => + { + s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText("mapped by "); + d.AddUserLink(metadata.Author); + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + settingChangeTracker?.Dispose(); + cancellationSource?.Cancel(); + } + } + } +} From c646f8479b35cc7d66b1075ad5c5ba8ab46434ad Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 7 Jan 2023 23:53:19 +0100 Subject: [PATCH 002/214] Move stardifficulty logic to main BeatmapInfoWedge class instead of text subclass ( for use by background star rating colour bar ) --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 4d1a2133fd87..104fa8787b30 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -37,10 +37,16 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer [Resolved] private IBindable ruleset { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + protected Container DisplayedContent { get; private set; } protected WedgeInfoText Info { get; private set; } + private IBindable starDifficulty; + private CancellationTokenSource cancellationSource; + public BeatmapInfoWedgeV2() { Shear = wedged_container_shear; @@ -98,6 +104,19 @@ public WorkingBeatmap Beatmap private Container loadingInfo; + protected override void LoadComplete() + { + base.LoadComplete(); + starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + cancellationSource?.Cancel(); + } + private void updateDisplay() { Scheduler.AddOnce(perform); @@ -127,7 +146,7 @@ void removeOldInfo() Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap), - Info = new WedgeInfoText(beatmap), + Info = new WedgeInfoText(beatmap, starDifficulty), } }, loaded => { @@ -154,26 +173,22 @@ public partial class WedgeInfoText : Container private ILocalisedBindableString artistBinding; private readonly WorkingBeatmap working; + private readonly IBindable starDifficulty; [Resolved] private IBindable> mods { get; set; } - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved] private OsuColour colours { get; set; } private ModSettingChangeTracker settingChangeTracker; - public WedgeInfoText(WorkingBeatmap working) + public WedgeInfoText(WorkingBeatmap working, IBindable starDifficulty) { this.working = working; + this.starDifficulty = starDifficulty; } - private CancellationTokenSource cancellationSource; - private IBindable starDifficulty; - [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { @@ -309,7 +324,6 @@ protected override void LoadComplete() difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); }, true); - starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; @@ -349,7 +363,6 @@ protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); settingChangeTracker?.Dispose(); - cancellationSource?.Cancel(); } } } From 0199c19f74705c2d5c0dd10f3c61c0c8325ef224 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 01:24:47 +0100 Subject: [PATCH 003/214] Add a test scene and move colour bar to back and adjust positioning of it --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 187 ++++++++++++++++++ osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 131 +++--------- 2 files changed, 217 insertions(+), 101 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs new file mode 100644 index 000000000000..98e9d803ca79 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -0,0 +1,187 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [TestFixture] + public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene + { + private RulesetStore rulesets; + private TestBeatmapInfoWedgeV2 infoWedge; + private readonly List beatmaps = new List(); + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(infoWedge = new TestBeatmapInfoWedgeV2 + { + Size = new Vector2(0.6f, 120), + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20 } + }); + + AddStep("show", () => infoWedge.Show()); + + selectBeatmap(Beatmap.Value.Beatmap); + + AddWaitStep("wait for select", 3); + + AddStep("hide", () => { infoWedge.Hide(); }); + + AddWaitStep("wait for hide", 3); + + AddStep("show", () => { infoWedge.Show(); }); + + AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => + { + foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType>()) + hasCurrentValue.Current.Value = new StarDifficulty(v, 0); + }); + + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + var instance = rulesetInfo.CreateInstance(); + var testBeatmap = createTestBeatmap(rulesetInfo); + + beatmaps.Add(testBeatmap); + + setRuleset(rulesetInfo); + + selectBeatmap(testBeatmap); + + testBeatmapLabels(instance); + } + } + + private void testBeatmapLabels(Ruleset ruleset) + { + AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("reset mods", () => SelectedMods.SetDefault()); + } + + [Test] + public void TestTruncation() + { + selectBeatmap(createLongMetadata()); + } + + private void setRuleset(RulesetInfo rulesetInfo) + { + Container containerBefore = null; + + AddStep("set ruleset", () => + { + // wedge content is only refreshed if the ruleset changes, so only wait for load in that case. + if (!rulesetInfo.Equals(Ruleset.Value)) + containerBefore = infoWedge.DisplayedContent; + + Ruleset.Value = rulesetInfo; + }); + + AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); + } + + private void selectBeatmap([CanBeNull] IBeatmap b) + { + Container containerBefore = null; + + AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => + { + containerBefore = infoWedge.DisplayedContent; + infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); + }); + + AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); + } + + private IBeatmap createTestBeatmap(RulesetInfo ruleset) + { + List objects = new List(); + for (double i = 0; i < 50000; i += 1000) + objects.Add(new TestHitObject { StartTime = i }); + + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Author = { Username = $"{ruleset.ShortName}Author" }, + Artist = $"{ruleset.ShortName}Artist", + Source = $"{ruleset.ShortName}Source", + Title = $"{ruleset.ShortName}Title" + }, + Ruleset = ruleset, + StarRating = 6, + DifficultyName = $"{ruleset.ShortName}Version", + Difficulty = new BeatmapDifficulty() + }, + HitObjects = objects + }; + } + + private IBeatmap createLongMetadata() + { + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Author = { Username = "WWWWWWWWWWWWWWW" }, + Artist = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist", + Source = "Verrrrry long Source", + Title = "Verrrrry long Title" + }, + DifficultyName = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", + Status = BeatmapOnlineStatus.Graveyard, + }, + }; + } + + private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2 + { + public new Container DisplayedContent => base.DisplayedContent; + + public new WedgeInfoText Info => base.Info; + } + + private class TestHitObject : ConvertHitObject, IHasPosition + { + public float X => 0; + public float Y => 0; + public Vector2 Position { get; } = Vector2.Zero; + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 104fa8787b30..5583ad11f799 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Threading; using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,18 +15,16 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Effects; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select { + [Cached] public partial class BeatmapInfoWedgeV2 : VisibilityContainer { - public const float BORDER_THICKNESS = 2.5f; private const float shear_width = 36.75f; private const float transition_duration = 250; @@ -44,22 +41,25 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer protected WedgeInfoText Info { get; private set; } - private IBindable starDifficulty; + private IBindable starDifficulty = new Bindable(); private CancellationTokenSource cancellationSource; + private readonly Container difficultyColourBar; + public BeatmapInfoWedgeV2() { + CornerRadius = 10; Shear = wedged_container_shear; Masking = true; - BorderColour = new Color4(221, 255, 255, 255); - BorderThickness = BORDER_THICKNESS; Alpha = 0; - EdgeEffect = new EdgeEffectParameters + Child = difficultyColourBar = new Container { - Type = EdgeEffectType.Glow, - Colour = new Color4(130, 204, 255, 150), - Radius = 20, - Roundness = 15, + Depth = float.MaxValue, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 40, + Child = new Box { RelativeSizeAxes = Axes.Both } }; } @@ -95,6 +95,7 @@ public WorkingBeatmap Beatmap if (beatmap == value) return; beatmap = value; + starDifficulty = difficultyCache.GetBindableDifficulty(value.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); updateDisplay(); } @@ -104,12 +105,6 @@ public WorkingBeatmap Beatmap private Container loadingInfo; - protected override void LoadComplete() - { - base.LoadComplete(); - starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -140,13 +135,15 @@ void removeOldInfo() LoadComponentAsync(loadingInfo = new Container { + Masking = true, + X = -30, + CornerRadius = 10, RelativeSizeAxes = Axes.Both, - Shear = -Shear, Depth = DisplayedContent?.Depth + 1 ?? 0, Children = new Drawable[] { - new BeatmapInfoWedgeBackground(beatmap), - Info = new WedgeInfoText(beatmap, starDifficulty), + new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, + Info = new WedgeInfoText(beatmap, starDifficulty) { Shear = -Shear } } }, loaded => { @@ -161,12 +158,9 @@ void removeOldInfo() public partial class WedgeInfoText : Container { - public OsuSpriteText VersionLabel { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } - public FillFlowContainer MapperContainer { get; private set; } - private Container difficultyColourBar; private StarRatingDisplay starRatingDisplay; private ILocalisedBindableString titleBinding; @@ -178,6 +172,9 @@ public partial class WedgeInfoText : Container [Resolved] private IBindable> mods { get; set; } + [Resolved] + private BeatmapInfoWedgeV2 wedge { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -200,51 +197,8 @@ private void load(LocalisationManager localisation) titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); - const float top_height = 0.7f; - Children = new Drawable[] { - difficultyColourBar = new Container - { - RelativeSizeAxes = Axes.Y, - Width = 20f, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Width = top_height, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Alpha = 0.5f, - X = top_height, - Width = 1 - top_height, - } - } - }, - new FillFlowContainer - { - Name = "Topleft-aligned metadata", - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 10, Left = 25, Right = shear_width * 2.5f }, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] - { - VersionLabel = new OsuSpriteText - { - Text = beatmapInfo.DifficultyName, - Font = OsuFont.GetFont(size: 24, italics: true), - RelativeSizeAxes = Axes.X, - Truncate = true, - }, - } - }, new FillFlowContainer { Name = "Topright-aligned metadata", @@ -279,12 +233,10 @@ private void load(LocalisationManager localisation) }, new FillFlowContainer { - Name = "Centre-aligned metadata", - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopLeft, - Y = -7, + Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Padding = new MarginPadding { Left = 25, Right = shear_width }, + Position = new Vector2(50, 12), + Width = .8f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] @@ -292,23 +244,17 @@ private void load(LocalisationManager localisation) TitleLabel = new OsuSpriteText { Current = { BindTarget = titleBinding }, - Font = OsuFont.GetFont(size: 28, italics: true), + Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true, + Truncate = true }, ArtistLabel = new OsuSpriteText { Current = { BindTarget = artistBinding }, - Font = OsuFont.GetFont(size: 17, italics: true), + //Not sure if this should be semi bold or medium + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true, - }, - MapperContainer = new FillFlowContainer - { - Margin = new MarginPadding { Top = 10 }, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Child = getMapper(metadata), + Truncate = true } } } @@ -321,9 +267,8 @@ protected override void LoadComplete() starRatingDisplay.DisplayedStars.BindValueChanged(s => { - difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue); + wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; @@ -343,22 +288,6 @@ protected override void LoadComplete() }, true); } - private Drawable getMapper(BeatmapMetadata metadata) - { - if (string.IsNullOrEmpty(metadata.Author.Username)) - return Empty(); - - return new LinkFlowContainer(s => - { - s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); - }).With(d => - { - d.AutoSizeAxes = Axes.Both; - d.AddText("mapped by "); - d.AddUserLink(metadata.Author); - }); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 61caabaa8ee4002c7d96859f0937b3fb8b997c82 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 01:47:22 +0100 Subject: [PATCH 004/214] Add coloured star counter --- .../Graphics/UserInterface/StarCounter.cs | 6 ++- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 51 +++++++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index d7d088d798d7..7adb482188ab 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -32,6 +32,11 @@ public partial class StarCounter : Container private const float star_spacing = 4; + public virtual FillDirection Direction + { + set => stars.Direction = value; + } + private float current; /// @@ -66,7 +71,6 @@ public StarCounter(int starCount = 10) stars = new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, Spacing = new Vector2(star_spacing), ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(_ => CreateStar()) } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 5583ad11f799..07fcb42fff73 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -3,7 +3,9 @@ #nullable disable +using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using osuTK; using osu.Framework.Allocation; @@ -17,6 +19,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -25,11 +28,12 @@ namespace osu.Game.Screens.Select [Cached] public partial class BeatmapInfoWedgeV2 : VisibilityContainer { - private const float shear_width = 36.75f; + private const float shear_width = 21; + private const int wedge_height = 120; private const float transition_duration = 250; - private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); [Resolved] private IBindable ruleset { get; set; } @@ -45,6 +49,7 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer private CancellationTokenSource cancellationSource; private readonly Container difficultyColourBar; + private readonly StarCounter starCounter; public BeatmapInfoWedgeV2() { @@ -52,14 +57,27 @@ public BeatmapInfoWedgeV2() Shear = wedged_container_shear; Masking = true; Alpha = 0; - Child = difficultyColourBar = new Container + + Children = new Drawable[] { - Depth = float.MaxValue, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 40, - Child = new Box { RelativeSizeAxes = Axes.Both } + difficultyColourBar = new Container + { + Depth = float.MaxValue, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 40, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + Scale = new Vector2(0.4f), + Shear = -wedged_container_shear, + X = -15, + Direction = FillDirection.Vertical + } }; } @@ -67,6 +85,15 @@ public BeatmapInfoWedgeV2() private void load() { ruleset.BindValueChanged(_ => updateDisplay()); + + float starAngle = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)); + + //Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container + starCounter.Children.First().Rotation = starAngle; + + //Makes sure the stars center themselves properly in the colour bar + starCounter.Children.First().Anchor = Anchor.Centre; + starCounter.Children.First().Origin = Anchor.Centre; } private const double animation_duration = 800; @@ -267,8 +294,12 @@ protected override void LoadComplete() starRatingDisplay.DisplayedStars.BindValueChanged(s => { - wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); + wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); + wedge.starCounter.Current = (float)s.NewValue; + + wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue), 750, Easing.OutQuint); }, true); + starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; From 1698272eb88b3937228783d3df8d04db993d5da5 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 12:03:02 +0100 Subject: [PATCH 005/214] Simplify passing data from BeatmapInfoWedgeV2.cs to subclass wedgeinfotext --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 07fcb42fff73..1056d9478bb8 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -60,17 +60,22 @@ public BeatmapInfoWedgeV2() Children = new Drawable[] { + //These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area difficultyColourBar = new Container { + Colour = Colour4.Transparent, Depth = float.MaxValue, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, + + //By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. Width = 40, Child = new Box { RelativeSizeAxes = Axes.Both } }, starCounter = new StarCounter { + Colour = Colour4.Transparent, Anchor = Anchor.CentreRight, Origin = Anchor.Centre, Scale = new Vector2(0.4f), @@ -170,7 +175,7 @@ void removeOldInfo() Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, - Info = new WedgeInfoText(beatmap, starDifficulty) { Shear = -Shear } + Info = new WedgeInfoText { Shear = -Shear } } }, loaded => { @@ -193,9 +198,6 @@ public partial class WedgeInfoText : Container private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; - private readonly WorkingBeatmap working; - private readonly IBindable starDifficulty; - [Resolved] private IBindable> mods { get; set; } @@ -207,17 +209,11 @@ public partial class WedgeInfoText : Container private ModSettingChangeTracker settingChangeTracker; - public WedgeInfoText(WorkingBeatmap working, IBindable starDifficulty) - { - this.working = working; - this.starDifficulty = starDifficulty; - } - [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { - var beatmapInfo = working.BeatmapInfo; - var metadata = beatmapInfo.Metadata; + var beatmapInfo = wedge.Beatmap.BeatmapInfo; + var metadata = wedge.beatmap.Metadata; RelativeSizeAxes = Axes.Both; @@ -262,7 +258,7 @@ private void load(LocalisationManager localisation) { Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Position = new Vector2(50, 12), + Position = new Vector2(80, 12), Width = .8f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -270,6 +266,7 @@ private void load(LocalisationManager localisation) { TitleLabel = new OsuSpriteText { + Shadow = true, Current = { BindTarget = titleBinding }, Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, @@ -277,6 +274,7 @@ private void load(LocalisationManager localisation) }, ArtistLabel = new OsuSpriteText { + Shadow = true, Current = { BindTarget = artistBinding }, //Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), @@ -297,10 +295,10 @@ protected override void LoadComplete() wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); wedge.starCounter.Current = (float)s.NewValue; - wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue), 750, Easing.OutQuint); + wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - starDifficulty.BindValueChanged(s => + wedge.starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; From 65c30d2c2e0a322d0d75003905fd44b4e5969d32 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 12:56:32 +0100 Subject: [PATCH 006/214] Remove nullability disabling --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 62 +++++++++---------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 1056d9478bb8..da7cfc6613f4 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -36,17 +34,11 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); [Resolved] - private IBindable ruleset { get; set; } - - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } + private IBindable ruleset { get; set; } = null!; - protected Container DisplayedContent { get; private set; } + protected Container? DisplayedContent { get; private set; } - protected WedgeInfoText Info { get; private set; } - - private IBindable starDifficulty = new Bindable(); - private CancellationTokenSource cancellationSource; + protected WedgeInfoText? Info { get; private set; } private readonly Container difficultyColourBar; private readonly StarCounter starCounter; @@ -117,9 +109,9 @@ protected override void PopOut() this.FadeOut(transition_duration * 2, Easing.In); } - private WorkingBeatmap beatmap; + private WorkingBeatmap? beatmap; - public WorkingBeatmap Beatmap + public WorkingBeatmap? Beatmap { get => beatmap; set @@ -127,7 +119,6 @@ public WorkingBeatmap Beatmap if (beatmap == value) return; beatmap = value; - starDifficulty = difficultyCache.GetBindableDifficulty(value.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); updateDisplay(); } @@ -135,14 +126,7 @@ public WorkingBeatmap Beatmap public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback - private Container loadingInfo; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - cancellationSource?.Cancel(); - } + private Container? loadingInfo; private void updateDisplay() { @@ -190,30 +174,36 @@ void removeOldInfo() public partial class WedgeInfoText : Container { - public OsuSpriteText TitleLabel { get; private set; } - public OsuSpriteText ArtistLabel { get; private set; } + public OsuSpriteText TitleLabel { get; private set; } = null!; + public OsuSpriteText ArtistLabel { get; private set; } = null!; + + private StarRatingDisplay starRatingDisplay = null!; - private StarRatingDisplay starRatingDisplay; + private ILocalisedBindableString titleBinding = null!; + private ILocalisedBindableString artistBinding = null!; - private ILocalisedBindableString titleBinding; - private ILocalisedBindableString artistBinding; + [Resolved] + private IBindable> mods { get; set; } = null!; [Resolved] - private IBindable> mods { get; set; } + private BeatmapInfoWedgeV2 wedge { get; set; } = null!; [Resolved] - private BeatmapInfoWedgeV2 wedge { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + + private ModSettingChangeTracker? settingChangeTracker; - private ModSettingChangeTracker settingChangeTracker; + private IBindable? starDifficulty; + private CancellationTokenSource? cancellationSource; [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { - var beatmapInfo = wedge.Beatmap.BeatmapInfo; - var metadata = wedge.beatmap.Metadata; + var beatmapInfo = wedge.Beatmap!.BeatmapInfo; + var metadata = wedge.beatmap!.Metadata; RelativeSizeAxes = Axes.Both; @@ -274,6 +264,7 @@ private void load(LocalisationManager localisation) }, ArtistLabel = new OsuSpriteText { + //figma design has a diffused shadow, instead of the solid one present here. Shadow = true, Current = { BindTarget = artistBinding }, //Not sure if this should be semi bold or medium @@ -298,7 +289,8 @@ protected override void LoadComplete() wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - wedge.starDifficulty.BindValueChanged(s => + starDifficulty = difficultyCache.GetBindableDifficulty(wedge.beatmap!.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; @@ -320,6 +312,8 @@ protected override void LoadComplete() protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + + cancellationSource?.Cancel(); settingChangeTracker?.Dispose(); } } From 9afdfd7f067c786f236748c84e7028e0c294ff76 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sun, 8 Jan 2023 15:42:42 +0100 Subject: [PATCH 007/214] small tweaks, container edge - effect addition.. --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index da7cfc6613f4..ced6931e1f47 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -27,9 +28,9 @@ namespace osu.Game.Screens.Select public partial class BeatmapInfoWedgeV2 : VisibilityContainer { private const float shear_width = 21; - private const int wedge_height = 120; - + private const float wedge_height = 120; private const float transition_duration = 250; + private const float corner_radius = 10; private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); @@ -45,14 +46,20 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer public BeatmapInfoWedgeV2() { - CornerRadius = 10; Shear = wedged_container_shear; Masking = true; - Alpha = 0; + EdgeEffect = new EdgeEffectParameters + { + Colour = Colour4.Black.Opacity(.25f), + Type = EdgeEffectType.Shadow, + Radius = corner_radius, + Roundness = corner_radius + }; + CornerRadius = corner_radius; Children = new Drawable[] { - //These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area + // These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area difficultyColourBar = new Container { Colour = Colour4.Transparent, @@ -61,7 +68,7 @@ public BeatmapInfoWedgeV2() Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, - //By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. + // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. Width = 40, Child = new Box { RelativeSizeAxes = Axes.Both } }, @@ -70,7 +77,7 @@ public BeatmapInfoWedgeV2() Colour = Colour4.Transparent, Anchor = Anchor.CentreRight, Origin = Anchor.Centre, - Scale = new Vector2(0.4f), + Scale = new Vector2(0.35f), Shear = -wedged_container_shear, X = -15, Direction = FillDirection.Vertical @@ -85,10 +92,10 @@ private void load() float starAngle = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)); - //Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container + // Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container starCounter.Children.First().Rotation = starAngle; - //Makes sure the stars center themselves properly in the colour bar + // Makes sure the stars center themselves properly in the colour bar starCounter.Children.First().Anchor = Anchor.Centre; starCounter.Children.First().Origin = Anchor.Centre; } @@ -153,13 +160,13 @@ void removeOldInfo() { Masking = true, X = -30, - CornerRadius = 10, + CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, - Info = new WedgeInfoText { Shear = -Shear } + Info = new WedgeInfoText(beatmap) { Shear = -Shear } } }, loaded => { @@ -182,11 +189,10 @@ public partial class WedgeInfoText : Container private ILocalisedBindableString titleBinding = null!; private ILocalisedBindableString artistBinding = null!; - [Resolved] - private IBindable> mods { get; set; } = null!; + private readonly WorkingBeatmap working; [Resolved] - private BeatmapInfoWedgeV2 wedge { get; set; } = null!; + private IBindable> mods { get; set; } = null!; [Resolved] private OsuColour colours { get; set; } = null!; @@ -194,16 +200,24 @@ public partial class WedgeInfoText : Container [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + [Resolved] + private BeatmapInfoWedgeV2 wedge { get; set; } = null!; + private ModSettingChangeTracker? settingChangeTracker; private IBindable? starDifficulty; private CancellationTokenSource? cancellationSource; + public WedgeInfoText(WorkingBeatmap working) + { + this.working = working; + } + [BackgroundDependencyLoader] private void load(LocalisationManager localisation) { - var beatmapInfo = wedge.Beatmap!.BeatmapInfo; - var metadata = wedge.beatmap!.Metadata; + var beatmapInfo = working.BeatmapInfo; + var metadata = working.Metadata; RelativeSizeAxes = Axes.Both; @@ -249,7 +263,7 @@ private void load(LocalisationManager localisation) Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, Position = new Vector2(80, 12), - Width = .8f, + Width = .7f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] @@ -289,7 +303,7 @@ protected override void LoadComplete() wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); - starDifficulty = difficultyCache.GetBindableDifficulty(wedge.beatmap!.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; From 2a82f618ed9a69dd28613dfc8f04189ceecd702f Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 10 Jan 2023 17:34:47 +0100 Subject: [PATCH 008/214] Add TODO for text margin const, added pertinent comments to known "issues" --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index ced6931e1f47..ce07a59a0ca1 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -32,6 +32,9 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer private const float transition_duration = 250; private const float corner_radius = 10; + /// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements + private const float text_margin = 62; + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); [Resolved] @@ -159,18 +162,21 @@ void removeOldInfo() LoadComponentAsync(loadingInfo = new Container { Masking = true, + // We offset this by the portion of the colour bar underneath we wish to show X = -30, CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, Children = new Drawable[] { + // TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft. + // pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered. new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, Info = new WedgeInfoText(beatmap) { Shear = -Shear } } }, loaded => { - // ensure we are the most recent loaded wedge. + // Ensure we are the most recent loaded wedge. if (loaded != loadingInfo) return; removeOldInfo(); @@ -262,7 +268,7 @@ private void load(LocalisationManager localisation) { Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Position = new Vector2(80, 12), + Position = new Vector2(text_margin + shear_width, 12), Width = .7f, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -278,10 +284,10 @@ private void load(LocalisationManager localisation) }, ArtistLabel = new OsuSpriteText { - //figma design has a diffused shadow, instead of the solid one present here. + // TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware. Shadow = true, Current = { BindTarget = artistBinding }, - //Not sure if this should be semi bold or medium + // Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, Truncate = true @@ -297,8 +303,8 @@ protected override void LoadComplete() starRatingDisplay.DisplayedStars.BindValueChanged(s => { - wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); wedge.starCounter.Current = (float)s.NewValue; + wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); From 8bfe24ced0c9163c1e1017b76d1f1669d57a160c Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 10 Jan 2023 17:49:33 +0100 Subject: [PATCH 009/214] Remove nullable disable in test. --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 98e9d803ca79..193acc8a7bd3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -24,8 +21,8 @@ namespace osu.Game.Tests.Visual.SongSelect [TestFixture] public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene { - private RulesetStore rulesets; - private TestBeatmapInfoWedgeV2 infoWedge; + private RulesetStore rulesets = null!; + private TestBeatmapInfoWedgeV2 infoWedge = null!; private readonly List beatmaps = new List(); [BackgroundDependencyLoader] @@ -80,8 +77,8 @@ protected override void LoadComplete() private void testBeatmapLabels(Ruleset ruleset) { - AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); - AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); + AddAssert("check title", () => infoWedge.Info!.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); } [SetUpSteps] @@ -98,7 +95,7 @@ public void TestTruncation() private void setRuleset(RulesetInfo rulesetInfo) { - Container containerBefore = null; + Container? containerBefore = null; AddStep("set ruleset", () => { @@ -112,9 +109,9 @@ private void setRuleset(RulesetInfo rulesetInfo) AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); } - private void selectBeatmap([CanBeNull] IBeatmap b) + private void selectBeatmap(IBeatmap? b) { - Container containerBefore = null; + Container? containerBefore = null; AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { @@ -172,9 +169,9 @@ private IBeatmap createLongMetadata() private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2 { - public new Container DisplayedContent => base.DisplayedContent; + public new Container? DisplayedContent => base.DisplayedContent; - public new WedgeInfoText Info => base.Info; + public new WedgeInfoText? Info => base.Info; } private class TestHitObject : ConvertHitObject, IHasPosition From 880428046a17606b200750c6f8bdec254fd7e4e1 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 10 Jan 2023 18:03:28 +0100 Subject: [PATCH 010/214] Fix margins on top right aligned elements. --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index ce07a59a0ca1..1497bed12144 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -238,7 +238,7 @@ private void load(LocalisationManager localisation) Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Direction = FillDirection.Vertical, - Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, + Padding = new MarginPadding { Top = 3, Right = 8 }, AutoSizeAxes = Axes.Both, Shear = wedged_container_shear, Spacing = new Vector2(0f, 5f), From 7510201804aab33e90d805b8a34b9883f9ff390f Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 16 Jan 2023 22:24:21 +0100 Subject: [PATCH 011/214] Add back null beatmap test --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 193acc8a7bd3..a2935fb218c7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -93,6 +94,15 @@ public void TestTruncation() selectBeatmap(createLongMetadata()); } + [Test] + public void TestNullBeatmap() + { + selectBeatmap(null); + AddAssert("check default title", () => infoWedge.Info!.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info!.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType().Any()); + } + private void setRuleset(RulesetInfo rulesetInfo) { Container? containerBefore = null; From 74b72e4ac0d6c34df51bf8d27c24d4625a9f8039 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 16 Jan 2023 22:46:18 +0100 Subject: [PATCH 012/214] Address issues that joehuu brought up --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index a2935fb218c7..4904e2a723a9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -38,7 +38,7 @@ protected override void LoadComplete() Add(infoWedge = new TestBeatmapInfoWedgeV2 { - Size = new Vector2(0.6f, 120), + Width = 0.6f, RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Top = 20 } }); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 1497bed12144..02d640fc0d6c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -31,6 +31,7 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer private const float wedge_height = 120; private const float transition_duration = 250; private const float corner_radius = 10; + private const float colour_bar_width = 30; /// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements private const float text_margin = 62; @@ -49,6 +50,7 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer public BeatmapInfoWedgeV2() { + Height = wedge_height; Shear = wedged_container_shear; Masking = true; EdgeEffect = new EdgeEffectParameters @@ -72,7 +74,7 @@ public BeatmapInfoWedgeV2() RelativeSizeAxes = Axes.Y, // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. - Width = 40, + Width = colour_bar_width + corner_radius, Child = new Box { RelativeSizeAxes = Axes.Both } }, starCounter = new StarCounter @@ -82,7 +84,7 @@ public BeatmapInfoWedgeV2() Origin = Anchor.Centre, Scale = new Vector2(0.35f), Shear = -wedged_container_shear, - X = -15, + X = -colour_bar_width / 2, Direction = FillDirection.Vertical } }; @@ -163,7 +165,7 @@ void removeOldInfo() { Masking = true, // We offset this by the portion of the colour bar underneath we wish to show - X = -30, + X = -colour_bar_width, CornerRadius = corner_radius, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, @@ -268,8 +270,7 @@ private void load(LocalisationManager localisation) { Name = "Top-left aligned metadata", Direction = FillDirection.Vertical, - Position = new Vector2(text_margin + shear_width, 12), - Width = .7f, + Padding = new MarginPadding { Horizontal = text_margin + shear_width, Top = 12 }, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Children = new Drawable[] From 0ac7cd7409e1c61cc0f8df03c1fa0b780adb43da Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Wed, 18 Jan 2023 13:55:52 +0100 Subject: [PATCH 013/214] Expose star difficulty to wedge to allow updating starcounter and background colour internally. --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 02d640fc0d6c..d90c002953f9 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -24,7 +24,6 @@ namespace osu.Game.Screens.Select { - [Cached] public partial class BeatmapInfoWedgeV2 : VisibilityContainer { private const float shear_width = 21; @@ -41,6 +40,9 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer [Resolved] private IBindable ruleset { get; set; } = null!; + [Resolved] + private OsuColour colours { get; set; } = null!; + protected Container? DisplayedContent { get; private set; } protected WedgeInfoText? Info { get; private set; } @@ -183,6 +185,14 @@ void removeOldInfo() removeOldInfo(); Add(DisplayedContent = loaded); + + Info.StarRatingDisplay.DisplayedStars.BindValueChanged(s => + { + starCounter.Current = (float)s.NewValue; + starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); + + difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); + }, true); }); } } @@ -192,7 +202,7 @@ public partial class WedgeInfoText : Container public OsuSpriteText TitleLabel { get; private set; } = null!; public OsuSpriteText ArtistLabel { get; private set; } = null!; - private StarRatingDisplay starRatingDisplay = null!; + public StarRatingDisplay StarRatingDisplay = null!; private ILocalisedBindableString titleBinding = null!; private ILocalisedBindableString artistBinding = null!; @@ -202,15 +212,9 @@ public partial class WedgeInfoText : Container [Resolved] private IBindable> mods { get; set; } = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; - [Resolved] - private BeatmapInfoWedgeV2 wedge { get; set; } = null!; - private ModSettingChangeTracker? settingChangeTracker; private IBindable? starDifficulty; @@ -246,7 +250,7 @@ private void load(LocalisationManager localisation) Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - starRatingDisplay = new StarRatingDisplay(default, animated: true) + StarRatingDisplay = new StarRatingDisplay(default, animated: true) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -302,24 +306,16 @@ protected override void LoadComplete() { base.LoadComplete(); - starRatingDisplay.DisplayedStars.BindValueChanged(s => - { - wedge.starCounter.Current = (float)s.NewValue; - wedge.starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); - - wedge.difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); - }, true); - starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { - starRatingDisplay.Current.Value = s.NewValue ?? default; + StarRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (!starRatingDisplay.IsPresent) - starRatingDisplay.FinishTransforms(true); + if (!StarRatingDisplay.IsPresent) + StarRatingDisplay.FinishTransforms(true); - starRatingDisplay.FadeIn(transition_duration); + StarRatingDisplay.FadeIn(transition_duration); }); mods.BindValueChanged(m => From 655242371b2858371c82970d9fe316eb7d26cd6d Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 23 Jan 2023 17:00:46 +0100 Subject: [PATCH 014/214] Buffer wedge content to avoid opacity issues when showing / hiding --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index d90c002953f9..63e414d6ad93 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -49,6 +49,7 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer private readonly Container difficultyColourBar; private readonly StarCounter starCounter; + private readonly BufferedContainer bufferedContent; public BeatmapInfoWedgeV2() { @@ -64,30 +65,35 @@ public BeatmapInfoWedgeV2() }; CornerRadius = corner_radius; - Children = new Drawable[] + // We want to buffer the wedge to avoid weird transparency overlaps between the colour bar and the background. + Child = bufferedContent = new BufferedContainer { - // These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area - difficultyColourBar = new Container - { - Colour = Colour4.Transparent, - Depth = float.MaxValue, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - - // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. - Width = colour_bar_width + corner_radius, - Child = new Box { RelativeSizeAxes = Axes.Both } - }, - starCounter = new StarCounter + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Colour = Colour4.Transparent, - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre, - Scale = new Vector2(0.35f), - Shear = -wedged_container_shear, - X = -colour_bar_width / 2, - Direction = FillDirection.Vertical + // These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area + difficultyColourBar = new Container + { + Colour = Colour4.Transparent, + Depth = float.MaxValue, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + + // By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it. + Width = colour_bar_width + corner_radius, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + starCounter = new StarCounter + { + Colour = Colour4.Transparent, + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + Scale = new Vector2(0.35f), + Shear = -wedged_container_shear, + X = -colour_bar_width / 2, + Direction = FillDirection.Vertical + } } }; } @@ -184,9 +190,9 @@ void removeOldInfo() if (loaded != loadingInfo) return; removeOldInfo(); - Add(DisplayedContent = loaded); + bufferedContent.Add(DisplayedContent = loaded); - Info.StarRatingDisplay.DisplayedStars.BindValueChanged(s => + Info.DisplayedStars.BindValueChanged(s => { starCounter.Current = (float)s.NewValue; starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); @@ -202,13 +208,15 @@ public partial class WedgeInfoText : Container public OsuSpriteText TitleLabel { get; private set; } = null!; public OsuSpriteText ArtistLabel { get; private set; } = null!; - public StarRatingDisplay StarRatingDisplay = null!; + private StarRatingDisplay starRatingDisplay = null!; private ILocalisedBindableString titleBinding = null!; private ILocalisedBindableString artistBinding = null!; private readonly WorkingBeatmap working; + public IBindable DisplayedStars => starRatingDisplay.DisplayedStars; + [Resolved] private IBindable> mods { get; set; } = null!; @@ -250,7 +258,7 @@ private void load(LocalisationManager localisation) Spacing = new Vector2(0f, 5f), Children = new Drawable[] { - StarRatingDisplay = new StarRatingDisplay(default, animated: true) + starRatingDisplay = new StarRatingDisplay(default, animated: true) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -309,13 +317,13 @@ protected override void LoadComplete() starDifficulty = difficultyCache.GetBindableDifficulty(working.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token); starDifficulty.BindValueChanged(s => { - StarRatingDisplay.Current.Value = s.NewValue ?? default; + starRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (!StarRatingDisplay.IsPresent) - StarRatingDisplay.FinishTransforms(true); + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); - StarRatingDisplay.FadeIn(transition_duration); + starRatingDisplay.FadeIn(transition_duration); }); mods.BindValueChanged(m => From c7d49bdc82c4ab48ab60040cdd0e161a481fdf94 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 30 Jan 2023 13:13:57 +0100 Subject: [PATCH 015/214] Update ```BeatmapInfoWedgeV2.cs``` animation to be similar to exit transition in ```SongSelect.cs`` --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 63e414d6ad93..6a1662fd6a08 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -113,20 +113,18 @@ private void load() starCounter.Children.First().Origin = Anchor.Centre; } - private const double animation_duration = 800; + private const double animation_duration = 600; protected override void PopIn() { this.MoveToX(0, animation_duration, Easing.OutQuint); - this.RotateTo(0, animation_duration, Easing.OutQuint); - this.FadeIn(transition_duration); + this.FadeIn(200, Easing.In); } protected override void PopOut() { - this.MoveToX(-100, animation_duration, Easing.In); - this.RotateTo(10, animation_duration, Easing.In); - this.FadeOut(transition_duration * 2, Easing.In); + this.MoveToX(-150, animation_duration, Easing.OutQuint); + this.FadeOut(200, Easing.OutQuint); } private WorkingBeatmap? beatmap; From 92690afa5fecd750583924c5917fc3b877b73041 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 30 Jan 2023 16:12:26 +0100 Subject: [PATCH 016/214] de-nest ```removeOldInfo()``` --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 6a1662fd6a08..41fe9d8d50ff 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -152,15 +152,6 @@ private void updateDisplay() void perform() { - void removeOldInfo() - { - State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; - - DisplayedContent?.FadeOut(transition_duration); - DisplayedContent?.Expire(); - DisplayedContent = null; - } - if (beatmap == null) { removeOldInfo(); @@ -199,6 +190,15 @@ void removeOldInfo() }, true); }); } + + void removeOldInfo() + { + State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; + + DisplayedContent?.FadeOut(transition_duration); + DisplayedContent?.Expire(); + DisplayedContent = null; + } } public partial class WedgeInfoText : Container From 27c52a45fc079174f29067ae6b7e97529f02cceb Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 30 Jan 2023 16:13:55 +0100 Subject: [PATCH 017/214] Use inline lambda for scheduling --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 41fe9d8d50ff..0cc60e4bba7f 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -148,9 +148,7 @@ public WorkingBeatmap? Beatmap private void updateDisplay() { - Scheduler.AddOnce(perform); - - void perform() + Scheduler.AddOnce(() => { if (beatmap == null) { @@ -189,7 +187,7 @@ void perform() difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); }); - } + }); void removeOldInfo() { From 5fc8f1d1bef944c43f1e9b1ac0456ded7015db72 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Fri, 3 Feb 2023 19:52:01 +0100 Subject: [PATCH 018/214] Fix ```BeatmapInfoWedgeV2.cs``` starCounter needing janky rotation application --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 0cc60e4bba7f..48a16d544913 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using osuTK; using osu.Framework.Allocation; @@ -84,15 +83,25 @@ public BeatmapInfoWedgeV2() Width = colour_bar_width + corner_radius, Child = new Box { RelativeSizeAxes = Axes.Both } }, - starCounter = new StarCounter + new Container { - Colour = Colour4.Transparent, - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre, - Scale = new Vector2(0.35f), + // Applying the shear to this container and nesting the starCounter inside avoids + // the deformation that occurs if the shear is applied to the starCounter whilst rotated Shear = -wedged_container_shear, X = -colour_bar_width / 2, - Direction = FillDirection.Vertical + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = colour_bar_width, + Child = starCounter = new StarCounter + { + Rotation = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)), + Colour = Colour4.Transparent, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.35f), + Direction = FillDirection.Vertical + } } } }; @@ -102,15 +111,6 @@ public BeatmapInfoWedgeV2() private void load() { ruleset.BindValueChanged(_ => updateDisplay()); - - float starAngle = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)); - - // Applying the rotation directly to the StarCounter distorts the stars, hence it is applied to the child container - starCounter.Children.First().Rotation = starAngle; - - // Makes sure the stars center themselves properly in the colour bar - starCounter.Children.First().Anchor = Anchor.Centre; - starCounter.Children.First().Origin = Anchor.Centre; } private const double animation_duration = 600; From de37a0a000bfb99ad0f56eaadbe9929eb93229ab Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Fri, 3 Feb 2023 19:53:04 +0100 Subject: [PATCH 019/214] enable pixelSnapping for the ```BufferedContainer``` in BeatmapInfoWedgeV2.cs --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 48a16d544913..a18d4086f765 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -65,7 +65,7 @@ public BeatmapInfoWedgeV2() CornerRadius = corner_radius; // We want to buffer the wedge to avoid weird transparency overlaps between the colour bar and the background. - Child = bufferedContent = new BufferedContainer + Child = bufferedContent = new BufferedContainer(pixelSnapping: true) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 38cc47d64ec35313d7cb928ae78a018b5641e2d5 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 4 Feb 2023 16:52:30 +0100 Subject: [PATCH 020/214] Remove ```IsPresent``` usages --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index a18d4086f765..fda20dde4d11 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -142,8 +142,6 @@ public WorkingBeatmap? Beatmap } } - public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback - private Container? loadingInfo; private void updateDisplay() @@ -316,7 +314,7 @@ protected override void LoadComplete() starRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (!starRatingDisplay.IsPresent) + if (starRatingDisplay.Alpha > 0) starRatingDisplay.FinishTransforms(true); starRatingDisplay.FadeIn(transition_duration); From cb679ccc2b95858b0ff85e86bffcd6ee6a27d9dd Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 11 Feb 2023 18:00:17 +0100 Subject: [PATCH 021/214] Separate wedge visibility test into its own method --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 4904e2a723a9..f99950dfb040 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -49,12 +50,6 @@ protected override void LoadComplete() AddWaitStep("wait for select", 3); - AddStep("hide", () => { infoWedge.Hide(); }); - - AddWaitStep("wait for hide", 3); - - AddStep("show", () => { infoWedge.Show(); }); - AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => { foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType>()) @@ -76,6 +71,26 @@ protected override void LoadComplete() } } + [Test] + public void TestWedgeVisibility() + { + AddStep("Make shadow red for test visibility", () => + { + infoWedge.EdgeEffect = new EdgeEffectParameters + { + Colour = Colour4.Red, + Type = EdgeEffectType.Shadow, + Radius = 5, + }; + }); + AddStep("hide", () => { infoWedge.Hide(); }); + AddWaitStep("wait for hide", 3); + AddAssert("check visibility", () => infoWedge.Alpha == 0); + AddStep("show", () => { infoWedge.Show(); }); + AddWaitStep("wait for show", 1); + AddAssert("check visibility", () => infoWedge.Alpha > 0); + } + private void testBeatmapLabels(Ruleset ruleset) { AddAssert("check title", () => infoWedge.Info!.TitleLabel.Current.Value == $"{ruleset.ShortName}Title"); From 468419896a5364252afa23263d9e43d26c4b7edf Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 11 Feb 2023 18:08:50 +0100 Subject: [PATCH 022/214] Separate ruleset changing tests into their own method. Add small clarification for edge colouring in visibility test --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index f99950dfb040..ebd8c008b38d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -39,22 +39,25 @@ protected override void LoadComplete() Add(infoWedge = new TestBeatmapInfoWedgeV2 { + State = { Value = Visibility.Visible }, Width = 0.6f, RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Top = 20 } }); - AddStep("show", () => infoWedge.Show()); - - selectBeatmap(Beatmap.Value.Beatmap); - - AddWaitStep("wait for select", 3); - AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => { foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType>()) hasCurrentValue.Current.Value = new StarDifficulty(v, 0); }); + } + + [Test] + public void TestRulesetChange() + { + selectBeatmap(Beatmap.Value.Beatmap); + + AddWaitStep("wait for select", 3); foreach (var rulesetInfo in rulesets.AvailableRulesets) { @@ -74,6 +77,8 @@ protected override void LoadComplete() [Test] public void TestWedgeVisibility() { + // Mostly just in case someone runs this test before others, + // leading to the shadow being very hard to see if it is black AddStep("Make shadow red for test visibility", () => { infoWedge.EdgeEffect = new EdgeEffectParameters From 09cb6ca3a797e516c3097b48883468be0f85f237 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Sat, 11 Feb 2023 18:15:21 +0100 Subject: [PATCH 023/214] Clean up formatting and wedge placement in testscene a tad, --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index ebd8c008b38d..3f3c7441f4d6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -42,7 +42,7 @@ protected override void LoadComplete() State = { Value = Visibility.Visible }, Width = 0.6f, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20 } + Margin = new MarginPadding { Top = 20, Left = -10 } }); AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => @@ -79,7 +79,7 @@ public void TestWedgeVisibility() { // Mostly just in case someone runs this test before others, // leading to the shadow being very hard to see if it is black - AddStep("Make shadow red for test visibility", () => + AddStep("make shadow red for test visibility", () => { infoWedge.EdgeEffect = new EdgeEffectParameters { From 299023fce036d6995ead30d00d1de6364bbcc137 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Tue, 21 Feb 2023 16:07:26 +0100 Subject: [PATCH 024/214] Improve visibility of wedge shading in test scene and fix an issue with excessive roundness on said shadow. --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 25 ++++++++++++++----- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 9 +++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 3f3c7441f4d6..09b93119cc20 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -37,12 +38,25 @@ protected override void LoadComplete() { base.LoadComplete(); - Add(infoWedge = new TestBeatmapInfoWedgeV2 + AddRange(new Drawable[] { - State = { Value = Visibility.Visible }, - Width = 0.6f, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20, Left = -10 } + // This exists only to make the wedge more visible in the test scene + new Box + { + Y = -20, + Colour = Colour4.Cornsilk.Darken(0.2f), + Height = BeatmapInfoWedgeV2.WEDGE_HEIGHT + 40, + Width = 0.65f, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20, Left = -10 } + }, + infoWedge = new TestBeatmapInfoWedgeV2 + { + State = { Value = Visibility.Visible }, + Width = 0.6f, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20, Left = -10 } + }, }); AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => @@ -200,7 +214,6 @@ private IBeatmap createLongMetadata() private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2 { public new Container? DisplayedContent => base.DisplayedContent; - public new WedgeInfoText? Info => base.Info; } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index fda20dde4d11..0a35e68c7e6b 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -25,8 +25,8 @@ namespace osu.Game.Screens.Select { public partial class BeatmapInfoWedgeV2 : VisibilityContainer { + public const float WEDGE_HEIGHT = 120; private const float shear_width = 21; - private const float wedge_height = 120; private const float transition_duration = 250; private const float corner_radius = 10; private const float colour_bar_width = 30; @@ -34,7 +34,7 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer /// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements private const float text_margin = 62; - private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / wedge_height, 0); + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / WEDGE_HEIGHT, 0); [Resolved] private IBindable ruleset { get; set; } = null!; @@ -52,7 +52,7 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer public BeatmapInfoWedgeV2() { - Height = wedge_height; + Height = WEDGE_HEIGHT; Shear = wedged_container_shear; Masking = true; EdgeEffect = new EdgeEffectParameters @@ -60,7 +60,6 @@ public BeatmapInfoWedgeV2() Colour = Colour4.Black.Opacity(.25f), Type = EdgeEffectType.Shadow, Radius = corner_radius, - Roundness = corner_radius }; CornerRadius = corner_radius; @@ -95,7 +94,7 @@ public BeatmapInfoWedgeV2() Width = colour_bar_width, Child = starCounter = new StarCounter { - Rotation = (float)(Math.Atan(shear_width / wedge_height) * (180 / Math.PI)), + Rotation = (float)(Math.Atan(shear_width / WEDGE_HEIGHT) * (180 / Math.PI)), Colour = Colour4.Transparent, Anchor = Anchor.Centre, Origin = Anchor.Centre, From f21238f517fa8e22a694affd4406bf3b858a8bb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Mar 2023 16:51:57 +0900 Subject: [PATCH 025/214] Adjust shadow to look better --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 0a35e68c7e6b..b7b60cffab6f 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -57,9 +57,9 @@ public BeatmapInfoWedgeV2() Masking = true; EdgeEffect = new EdgeEffectParameters { - Colour = Colour4.Black.Opacity(.25f), + Colour = Colour4.Black.Opacity(0.2f), Type = EdgeEffectType.Shadow, - Radius = corner_radius, + Radius = 3, }; CornerRadius = corner_radius; From 768d7b5e1c30329280447dc452fde063b6e440b3 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Mon, 17 Jul 2023 23:31:21 -0400 Subject: [PATCH 026/214] correct implementation of stable notelock --- .../TestSceneHitCircle.cs | 2 +- .../Objects/Drawables/DrawableHitCircle.cs | 7 ++- .../Objects/Drawables/DrawableOsuHitObject.cs | 10 +++-- .../Objects/Drawables/DrawableSliderHead.cs | 3 +- osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs | 2 +- osu.Game.Rulesets.Osu/UI/ClickAction.cs | 18 ++++++++ osu.Game.Rulesets.Osu/UI/IHitPolicy.cs | 2 +- .../UI/ObjectOrderedHitPolicy.cs | 45 ++++++++++--------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- .../UI/StartTimeOrderedHitPolicy.cs | 8 ++-- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- osu.Game/Rulesets/UI/IHitObjectContainer.cs | 2 +- 12 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/ClickAction.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 0314afc1acbe..c818a361df35 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -131,7 +131,7 @@ public TestDrawableHitCircle(HitCircle h, bool auto, double hitOffset) protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) != false) + if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) == ClickAction.Hit) { // force success ApplyResult(r => r.Type = HitResult.Great); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3458069dd187..09d818def889 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu.Judgements; 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 osu.Game.Skinning; using osuTK; @@ -154,13 +155,17 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) } var result = ResultFor(timeOffset); + var clickAction = CheckHittable?.Invoke(this, Time.Current); - if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false) + if (clickAction == ClickAction.Shake || (result == HitResult.None && clickAction != ClickAction.Ignore)) { Shake(); return; } + if (result == HitResult.None) + return; + ApplyResult(r => { var circleResult = (OsuHitCircleJudgementResult)r; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index df0ba344d8b6..a8ce2118c83c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.UI; using osuTK; using osuTK.Graphics; @@ -30,10 +31,13 @@ public abstract partial class DrawableOsuHitObject : DrawableHitObject CalculateDrawableRelativePosition(this); /// - /// Whether this can be hit, given a time value. - /// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false. + /// What action this should take in response to a + /// click at the given time value. + /// If non-null, judgements will be ignored for return values of + /// and , and this hit object will be shaken for return values of + /// . /// - public Func CheckHittable; + public Func CheckHittable; protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index b8a1efabe0ad..a4cf69ee3159 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -60,7 +61,7 @@ protected override void OnApply() pathVersion.BindTo(DrawableSlider.PathVersion); - CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true; + CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? ClickAction.Hit; } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs index afa54c2dfb99..7503c43e0b85 100644 --- a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs @@ -15,7 +15,7 @@ public class AnyOrderHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } - public bool IsHittable(DrawableHitObject hitObject, double time) => true; + public ClickAction CheckHittable(DrawableHitObject hitObject, double time) => ClickAction.Hit; public void HandleHit(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/UI/ClickAction.cs b/osu.Game.Rulesets.Osu/UI/ClickAction.cs new file mode 100644 index 000000000000..2b00f5acce53 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ClickAction.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.UI +{ + /// + /// An action that an recommends be taken in response to a click + /// on a . + /// + public enum ClickAction + { + Ignore, + Shake, + Hit + } +} diff --git a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs index b509796742c7..9820b8c1886d 100644 --- a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs @@ -20,7 +20,7 @@ public interface IHitPolicy /// The to check. /// The time to check. /// Whether can be hit at the given . - bool IsHittable(DrawableHitObject hitObject, double time); + ClickAction CheckHittable(DrawableHitObject hitObject, double time); /// /// Handles a being hit. diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs index 6330208d37b0..07942954e190 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -3,8 +3,7 @@ #nullable disable -using System.Collections.Generic; -using System.Linq; +using System; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -22,35 +21,41 @@ public class ObjectOrderedHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } - public bool IsHittable(DrawableHitObject hitObject, double time) => enumerateHitObjectsUpTo(hitObject.HitObject.StartTime).All(obj => obj.AllJudged); - public void HandleHit(DrawableHitObject hitObject) { } - private IEnumerable enumerateHitObjectsUpTo(double targetTime) + public ClickAction CheckHittable(DrawableHitObject hitObject, double time) { - foreach (var obj in HitObjectContainer.AliveObjects) + int index = HitObjectContainer.AliveObjects.IndexOf(hitObject); + + if (index > 0) { - if (obj.HitObject.StartTime >= targetTime) - yield break; + var previousHitObject = (DrawableOsuHitObject)HitObjectContainer.AliveObjects[index - 1]; + if (previousHitObject.HitObject.StackHeight > 0 && !previousHitObject.AllJudged) + return ClickAction.Ignore; + } - switch (obj) - { - case DrawableSpinner: - continue; + foreach (DrawableHitObject testObject in HitObjectContainer.AliveObjects) + { + if (testObject.AllJudged) + continue; - case DrawableSlider slider: - yield return slider.HeadCircle; + // if we found the object being checked, we can move on to the final timing test. + if (testObject == hitObject) + break; - break; + // for all other objects, we check for validity and block the hit if any are still valid. + // 3ms of extra leniency to account for slightly unsnapped objects. + if (testObject.HitObject.GetEndTime() + 3 < hitObject.HitObject.StartTime) + return ClickAction.Shake; + } - default: - yield return obj; + // stable has `const HitObjectManager.HITTABLE_RANGE = 400;`, which is only used for notelock code. + // probably not a coincidence that this is equivalent to lazer's OsuHitWindows.MISS_WINDOW. - break; - } - } + // TODO stable compares to 200 when autopilot is enabled, instead of 400. + return Math.Abs(hitObject.HitObject.StartTime - time) < 400 ? ClickAction.Hit : ClickAction.Shake; } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ed02284a4b91..15ca0a90de91 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -89,7 +89,7 @@ public IHitPolicy HitPolicy protected override void OnNewDrawableHitObject(DrawableHitObject drawable) { - ((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable; + ((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.CheckHittable; Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}"); drawable.OnLoadComplete += onDrawableHitObjectLoaded; diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index edc3ba08180c..f33ca58aeff2 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -24,7 +24,7 @@ public class StartTimeOrderedHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } - public bool IsHittable(DrawableHitObject hitObject, double time) + public ClickAction CheckHittable(DrawableHitObject hitObject, double time) { DrawableHitObject blockingObject = null; @@ -36,13 +36,13 @@ public bool IsHittable(DrawableHitObject hitObject, double time) // If there is no previous hitobject, allow the hit. if (blockingObject == null) - return true; + return ClickAction.Hit; // A hit is allowed if: // 1. The last blocking hitobject has been judged. // 2. The current time is after the last hitobject's start time. // Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245). - return blockingObject.Judged || time >= blockingObject.HitObject.StartTime; + return (blockingObject.Judged || time >= blockingObject.HitObject.StartTime) ? ClickAction.Hit : ClickAction.Shake; } public void HandleHit(DrawableHitObject hitObject) @@ -51,7 +51,7 @@ public void HandleHit(DrawableHitObject hitObject) if (!hitObjectCanBlockFutureHits(hitObject)) return; - if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) + if (CheckHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset) != ClickAction.Hit) throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); // Miss all hitobjects prior to the hit one. diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 099be486b355..454a83bcda83 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -21,7 +21,7 @@ public partial class HitObjectContainer : PooledDrawableWithLifetimeContainer Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); + public IList AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime).ToList(); /// /// Invoked when a is judged. diff --git a/osu.Game/Rulesets/UI/IHitObjectContainer.cs b/osu.Game/Rulesets/UI/IHitObjectContainer.cs index 6dcb0944be36..bb4806206da8 100644 --- a/osu.Game/Rulesets/UI/IHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/IHitObjectContainer.cs @@ -19,6 +19,6 @@ public interface IHitObjectContainer /// /// If this uses pooled objects, this is equivalent to . /// - IEnumerable AliveObjects { get; } + IList AliveObjects { get; } } } From 15af85226ce9c15c6bdb18d1c727cd257683d4e1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 19 Jul 2023 02:06:29 -0400 Subject: [PATCH 027/214] adjust test for correct stable notelock stable actually allows for hitobjs to be hit in the middle of sliders, as long as it doesn't interfere with the end time of the slider. --- .../TestSceneObjectOrderedHitPolicy.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index ee7044168848..be2affa50f67 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -213,10 +213,10 @@ public void TestClickSecondCircleAfterFirstCircleTimeWithFirstCircleJudged() } /// - /// Tests clicking a future circle after a slider's start time, but hitting all slider ticks. + /// Tests clicking a future circle after a slider's start time, but hitting the slider head and all slider ticks. /// [Test] - public void TestMissSliderHeadAndHitAllSliderTicks() + public void TestHitCircleBeforeSliderHead() { const double time_slider = 1500; const double time_circle = 1510; @@ -248,7 +248,7 @@ public void TestMissSliderHeadAndHitAllSliderTicks() new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } } }); - addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); From 2c97ac74107c975f8f37e6a840caff06bef08fc8 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 19 Jul 2023 14:28:04 -0400 Subject: [PATCH 028/214] convert AliveObjects to list in hit policy instead of globally --- osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs | 8 +++++--- osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 +- osu.Game/Rulesets/UI/IHitObjectContainer.cs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs index 07942954e190..172e5a39d885 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -27,16 +28,17 @@ public void HandleHit(DrawableHitObject hitObject) public ClickAction CheckHittable(DrawableHitObject hitObject, double time) { - int index = HitObjectContainer.AliveObjects.IndexOf(hitObject); + var aliveObjects = HitObjectContainer.AliveObjects.ToList(); + int index = aliveObjects.IndexOf(hitObject); if (index > 0) { - var previousHitObject = (DrawableOsuHitObject)HitObjectContainer.AliveObjects[index - 1]; + var previousHitObject = (DrawableOsuHitObject)aliveObjects[index - 1]; if (previousHitObject.HitObject.StackHeight > 0 && !previousHitObject.AllJudged) return ClickAction.Ignore; } - foreach (DrawableHitObject testObject in HitObjectContainer.AliveObjects) + foreach (DrawableHitObject testObject in aliveObjects) { if (testObject.AllJudged) continue; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 454a83bcda83..099be486b355 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -21,7 +21,7 @@ public partial class HitObjectContainer : PooledDrawableWithLifetimeContainer Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IList AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime).ToList(); + public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); /// /// Invoked when a is judged. diff --git a/osu.Game/Rulesets/UI/IHitObjectContainer.cs b/osu.Game/Rulesets/UI/IHitObjectContainer.cs index bb4806206da8..6dcb0944be36 100644 --- a/osu.Game/Rulesets/UI/IHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/IHitObjectContainer.cs @@ -19,6 +19,6 @@ public interface IHitObjectContainer /// /// If this uses pooled objects, this is equivalent to . /// - IList AliveObjects { get; } + IEnumerable AliveObjects { get; } } } From 6a8123029854e79f9124ea90f88c41e0245fda16 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 19 Jul 2023 14:44:28 -0400 Subject: [PATCH 029/214] rename ObjectOrderedHitPolicy to LegacyHitPolicy --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- .../UI/{ObjectOrderedHitPolicy.cs => LegacyHitPolicy.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/UI/{ObjectOrderedHitPolicy.cs => LegacyHitPolicy.cs} (97%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 250d97c5373b..229f80c2bd86 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -57,7 +57,7 @@ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset var osuRuleset = (DrawableOsuRuleset)drawableRuleset; if (ClassicNoteLock.Value) - osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); + osuRuleset.Playfield.HitPolicy = new LegacyHitPolicy(); usingHiddenFading = drawableRuleset.Mods.OfType().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false; } diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs similarity index 97% rename from osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs rename to osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs index 172e5a39d885..c35d4a1b56db 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.UI /// Hits will be blocked until the previous s have been judged. /// /// - public class ObjectOrderedHitPolicy : IHitPolicy + public class LegacyHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } From b15a54c91428cd8a0af019f6acc17c3b0a21318b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 11:36:00 +0900 Subject: [PATCH 030/214] Use new overlay pop-in/pop-out samples --- osu.Game/Overlays/ChatOverlay.cs | 3 +++ osu.Game/Overlays/WaveOverlayContainer.cs | 1 + osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 3 +++ 3 files changed, 7 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 87df08ceec37..a47d10c565f8 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -55,6 +55,9 @@ public partial class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComp private const float side_bar_width = 190; private const float chat_bar_height = 60; + protected override string PopInSampleName => @"UI/overlay-big-pop-in"; + protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + [Resolved] private OsuConfigManager config { get; set; } = null!; diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 153f7f541299..848d9e60b7f2 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -19,6 +19,7 @@ public abstract partial class WaveOverlayContainer : OsuFocusedOverlayContainer protected override bool StartHidden => true; protected override string PopInSampleName => "UI/wave-pop-in"; + protected override string PopOutSampleName => "UI/overlay-big-pop-out"; public const float HORIZONTAL_PADDING = 50; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 5753c268d9ca..7b631ebfea8c 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -32,6 +32,9 @@ public partial class BeatmapOptionsOverlay : OsuFocusedOverlayContainer public override bool BlockScreenWideMouse => false; + protected override string PopInSampleName => "SongSelect/options-pop-in"; + protected override string PopOutSampleName => "SongSelect/options-pop-out"; + public BeatmapOptionsOverlay() { AutoSizeAxes = Axes.Y; From 04a1f6a5085e0cc076abba71068d4b8d1031c0ab Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 11:37:07 +0900 Subject: [PATCH 031/214] Add panning to certain overlay pop-in/pop-outs --- .../Containers/OsuFocusedOverlayContainer.cs | 15 +++++++++++---- osu.Game/Overlays/LoginOverlay.cs | 2 ++ osu.Game/Overlays/NotificationOverlay.cs | 2 ++ osu.Game/Overlays/SettingsPanel.cs | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index f92cfc230622..162c4b6a5914 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -24,6 +24,7 @@ public abstract partial class OsuFocusedOverlayContainer : FocusedOverlayContain private Sample samplePopOut; protected virtual string PopInSampleName => "UI/overlay-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + protected virtual double PopInOutSampleBalance => 0; protected override bool BlockNonPositionalInput => true; @@ -133,15 +134,21 @@ protected override void UpdateState(ValueChangedEvent state) return; } - if (didChange) - samplePopIn?.Play(); + if (didChange && samplePopIn != null) + { + samplePopIn.Balance.Value = PopInOutSampleBalance; + samplePopIn.Play(); + } if (BlockScreenWideMouse && DimMainContent) overlayManager?.ShowBlockingOverlay(this); break; case Visibility.Hidden: - if (didChange) - samplePopOut?.Play(); + if (didChange && samplePopOut != null) + { + samplePopOut.Balance.Value = PopInOutSampleBalance; + samplePopOut.Play(); + } if (BlockScreenWideMouse) overlayManager?.HideBlockingOverlay(this); break; diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index a575253e712f..8a4bda89d9e8 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -20,6 +20,8 @@ public partial class LoginOverlay : OsuFocusedOverlayContainer private const float transition_time = 400; + protected override double PopInOutSampleBalance => OsuGameBase.SFX_STEREO_STRENGTH; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index c9d09848f847..ef181830b7b4 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -31,6 +31,8 @@ public partial class NotificationOverlay : OsuFocusedOverlayContainer, INamedOve public LocalisableString Title => NotificationsStrings.HeaderTitle; public LocalisableString Description => NotificationsStrings.HeaderDescription; + protected override double PopInOutSampleBalance => OsuGameBase.SFX_STEREO_STRENGTH; + public const float WIDTH = 320; public const float TRANSITION_LENGTH = 600; diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index d7f39a9d8fd2..58c56a5514b3 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -56,6 +56,7 @@ public abstract partial class SettingsPanel : OsuFocusedOverlayContainer private SeekLimitedSearchTextBox searchTextBox; protected override string PopInSampleName => "UI/settings-pop-in"; + protected override double PopInOutSampleBalance => -OsuGameBase.SFX_STEREO_STRENGTH; private readonly bool showSidebar; From 3d7ba0e18cdc9c739119125243c73a205e3f4fcb Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 17:03:45 +0900 Subject: [PATCH 032/214] Add pop-in/pop-out sfx to more overlays --- .../Collections/ManageCollectionsDialog.cs | 3 +++ .../Graphics/UserInterfaceV2/OsuPopover.cs | 25 ++++++++++++++++++- .../Screens/Edit/Setup/LabelledFileChooser.cs | 3 +++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 ++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 31016b807b7f..cc0f23d03061 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -23,6 +23,9 @@ public partial class ManageCollectionsDialog : OsuFocusedOverlayContainer private AudioFilter lowPassFilter = null!; + protected override string PopInSampleName => @"UI/overlay-big-pop-in"; + protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + public ManageCollectionsDialog() { Anchor = Anchor.Centre; diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 381193d5399f..2aa42d0d5055 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -21,6 +23,16 @@ public partial class OsuPopover : Popover, IKeyBindingHandler private const float fade_duration = 250; private const double scale_duration = 500; + private Sample? samplePopIn; + private Sample? samplePopOut; + protected virtual string PopInSampleName => "UI/overlay-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + + // required due to LoadAsyncComplete() calling PopOut() during load - similar workaround to `OsuDropdownMenu` + private bool wasOpened; + + protected virtual bool PlayPopInOutSamples => true; + public OsuPopover(bool withPadding = true) { Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); @@ -38,9 +50,11 @@ public OsuPopover(bool withPadding = true) } [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider? colourProvider, OsuColour colours) + private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio) { Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeaFoamDarker; + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); } protected override Drawable CreateArrow() => Empty(); @@ -49,12 +63,21 @@ protected override void PopIn() { this.ScaleTo(1, scale_duration, Easing.OutElasticHalf); this.FadeIn(fade_duration, Easing.OutQuint); + + if (PlayPopInOutSamples) + { + samplePopIn?.Play(); + wasOpened = true; + } } protected override void PopOut() { this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint); + + if (wasOpened && PlayPopInOutSamples) + samplePopOut?.Play(); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs index d14357e87585..61f33c4bdcda 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs @@ -114,6 +114,9 @@ protected override void Dispose(bool isDisposing) private partial class FileChooserPopover : OsuPopover { + protected override string PopInSampleName => "UI/overlay-big-pop-in"; + protected override string PopOutSampleName => "UI/overlay-big-pop-out"; + public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) { Child = new Container diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 70e4b2a589fd..030408de8416 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -191,6 +191,8 @@ public partial class PasswordEntryPopover : OsuPopover protected override bool BlockNonPositionalInput => true; + protected override bool PlayPopInOutSamples => false; + public PasswordEntryPopover(Room room) { this.room = room; From d10d7b6ea41f994c586725e127647b171634fae9 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 17:07:02 +0900 Subject: [PATCH 033/214] Change some component samples to go better with overlay pop-in samples --- osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs | 4 ++-- osu.Game/Graphics/UserInterface/ShearedToggleButton.cs | 5 +++++ osu.Game/Overlays/Mods/AddPresetButton.cs | 2 ++ .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index de4df96942a1..0eec04541c42 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -46,8 +46,8 @@ protected Color4 HoverColour private readonly Container content; private readonly Box hover; - public OsuAnimatedButton() - : base(HoverSampleSet.Button) + public OsuAnimatedButton(HoverSampleSet sampleSet = HoverSampleSet.Button) + : base(sampleSet) { base.Content.Add(content = new Container { diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index d5e0abe9d8f9..07ee26749a7f 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -14,6 +14,8 @@ public partial class ShearedToggleButton : ShearedButton private Sample? sampleOff; private Sample? sampleOn; + protected virtual bool PlayClickSampleOnly => false; + /// /// Whether this button is currently toggled to an active state. /// @@ -68,6 +70,9 @@ private void playSample() { sampleClick?.Play(); + if (PlayClickSampleOnly) + return; + if (Active.Value) sampleOn?.Play(); else diff --git a/osu.Game/Overlays/Mods/AddPresetButton.cs b/osu.Game/Overlays/Mods/AddPresetButton.cs index 731079d1d9fc..9063fd54f54f 100644 --- a/osu.Game/Overlays/Mods/AddPresetButton.cs +++ b/osu.Game/Overlays/Mods/AddPresetButton.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays.Mods { public partial class AddPresetButton : ShearedToggleButton, IHasPopover { + protected override bool PlayClickSampleOnly => true; + [Resolved] private OsuColour colours { get; set; } = null!; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 59b0bd17854e..e36f1e9cad72 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -262,6 +262,7 @@ internal partial class DivisorDisplay : OsuAnimatedButton, IHasPopover private readonly OsuSpriteText divisorText; public DivisorDisplay() + : base(HoverSampleSet.Default) { Anchor = Anchor.Centre; Origin = Anchor.Centre; From c811546868cb8754a32049bdfbd9bc22ab0be171 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Aug 2023 21:09:50 +0900 Subject: [PATCH 034/214] 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 a5a9387e36d6..a2afcd7a9fd6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From fc2fac577fcdb1d329b83e2cb2e30fc5e7ea78ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Aug 2023 17:05:25 +0900 Subject: [PATCH 035/214] Inverse and xmldoc `ShearedToggleButton` sample allowance bool --- .../UserInterface/ShearedToggleButton.cs | 20 +++++++++++-------- osu.Game/Overlays/Mods/AddPresetButton.cs | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index 07ee26749a7f..05ed531d0232 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -14,7 +14,11 @@ public partial class ShearedToggleButton : ShearedButton private Sample? sampleOff; private Sample? sampleOn; - protected virtual bool PlayClickSampleOnly => false; + /// + /// Sheared toggle buttons by default play two samples when toggled: a click and a toggle (on/off). + /// Sometimes this might be too much. Setting this to false will silence the toggle sound. + /// + protected virtual bool PlayToggleSamples => true; /// /// Whether this button is currently toggled to an active state. @@ -70,13 +74,13 @@ private void playSample() { sampleClick?.Play(); - if (PlayClickSampleOnly) - return; - - if (Active.Value) - sampleOn?.Play(); - else - sampleOff?.Play(); + if (PlayToggleSamples) + { + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } } } } diff --git a/osu.Game/Overlays/Mods/AddPresetButton.cs b/osu.Game/Overlays/Mods/AddPresetButton.cs index 9063fd54f54f..276afd9bece1 100644 --- a/osu.Game/Overlays/Mods/AddPresetButton.cs +++ b/osu.Game/Overlays/Mods/AddPresetButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Mods { public partial class AddPresetButton : ShearedToggleButton, IHasPopover { - protected override bool PlayClickSampleOnly => true; + protected override bool PlayToggleSamples => false; [Resolved] private OsuColour colours { get; set; } = null!; From 75750957c7cd93a2cac7d0090d146c214239eed2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Aug 2023 17:09:13 +0900 Subject: [PATCH 036/214] Add note about why pop in samples are disabled for `PasswordEntryPopover` --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 030408de8416..a45583a2ece4 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -191,6 +191,8 @@ public partial class PasswordEntryPopover : OsuPopover protected override bool BlockNonPositionalInput => true; + // When a room is clicked, it already plays a click sound, which clashes pretty badly with the pop in sound. + // Dunno about this one. I'd probably remove the click sound from the panel in cases they are password protected and play these pop in / out sounds. protected override bool PlayPopInOutSamples => false; public PasswordEntryPopover(Room room) From d2798c7a1ca1d9c79438fd8319662b5aba098c4e Mon Sep 17 00:00:00 2001 From: Wleter Date: Sun, 20 Aug 2023 17:55:19 +0200 Subject: [PATCH 037/214] don't allow negative scaling --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 72216f040e2a..a952cf3035b7 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -71,6 +71,9 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) scale.Y = scale.X / selectionRect.Width * selectionRect.Height; } + // If scaling reverses the selection, don't scale. + if (adjustedRect.Width + scale.X < 0 || adjustedRect.Height + scale.Y < 0) return true; + if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; @@ -79,8 +82,8 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( - MathF.Max(adjustedRect.Width / selectionRect.Width, 0), - MathF.Max(adjustedRect.Height / selectionRect.Height, 0) + adjustedRect.Width / selectionRect.Width, + adjustedRect.Height / selectionRect.Height ); foreach (var b in SelectedBlueprints) From 9f4f81c150895ddc08bb4680bbcbd981a03f9d0d Mon Sep 17 00:00:00 2001 From: Wleter Date: Mon, 21 Aug 2023 19:36:11 +0200 Subject: [PATCH 038/214] accumulating negative scaling --- .../SkinEditor/SkinSelectionHandler.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index a952cf3035b7..c90a1d8edf26 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -31,6 +31,8 @@ public partial class SkinSelectionHandler : SelectionHandler mo public static void ApplyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable)); + protected override void OnOperationEnded() + { + base.OnOperationEnded(); + accumulatedNegativeScaling = 0; + } + protected override void OnSelectionChanged() { base.OnSelectionChanged(); From 290d18ad690accc2a54c3290e3403f6c2534fb45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 17:31:19 +0900 Subject: [PATCH 039/214] Split out difficulties in beatmap carousel in a bit of a hacky way Seems like the simplest path forward for now, without a full rewrite. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 82 ++++++++++++++++------ osu.Game/Screens/Select/FilterCriteria.cs | 5 ++ 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9af9a0ce72af..d1a9b4176b62 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -78,6 +78,8 @@ public partial class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler originalBeatmapSetsDetached = Enumerable.Empty(); + /// /// Raised when the is changed. /// @@ -127,13 +129,29 @@ public IEnumerable BeatmapSets private void loadBeatmapSets(IEnumerable beatmapSets) { + originalBeatmapSetsDetached = beatmapSets.Detach(); + CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddItems(beatmapSets.Select(s => createCarouselSet(s.Detach())).OfType()); + if (beatmapsSplitOut) + { + var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b => + { + var set = new BeatmapSetInfo(new[] { b }); + return createCarouselSet(set); + }).OfType(); + + newRoot.AddItems(carouselBeatmapSets); + } + else + { + var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType(); + newRoot.AddItems(carouselBeatmapSets); + } root = newRoot; - if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) + if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; Scroll.Clear(false); @@ -330,8 +348,8 @@ private void beatmapsChanged(IRealmCollection sender, ChangeSet? ch // Only require to action here if the beatmap is missing. // This avoids processing these events unnecessarily when new beatmaps are imported, for example. - if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSet) - && existingSet.BeatmapSet.Beatmaps.All(b => b.ID != beatmapInfo.ID)) + if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets) + && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) { UpdateBeatmapSet(beatmapSet.Detach()); } @@ -345,15 +363,18 @@ public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => { - if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) + if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets)) return; - foreach (var beatmap in existingSet.Beatmaps) - randomSelectedBeatmaps.Remove(beatmap); + foreach (var set in existingSets) + { + foreach (var beatmap in set.Beatmaps) + randomSelectedBeatmaps.Remove(beatmap); + previouslyVisitedRandomSets.Remove(set); - previouslyVisitedRandomSets.Remove(existingSet); + root.RemoveItem(set); + } - root.RemoveItem(existingSet); itemsCache.Invalidate(); if (!Scroll.UserScrolling) @@ -371,13 +392,16 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); - var removedSet = root.RemoveChild(beatmapSet.ID); + var removedSets = root.RemoveChild(beatmapSet.ID); - // If we don't remove this here, it may remain in a hidden state until scrolled off screen. - // Doesn't really affect anything during actual user interaction, but makes testing annoying. - var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); - if (removedDrawable != null) - expirePanelImmediately(removedDrawable); + foreach (var removedSet in removedSets) + { + // If we don't remove this here, it may remain in a hidden state until scrolled off screen. + // Doesn't really affect anything during actual user interaction, but makes testing annoying. + var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); + if (removedDrawable != null) + expirePanelImmediately(removedDrawable); + } if (newSet != null) { @@ -632,6 +656,8 @@ public void Filter(FilterCriteria? newCriteria, bool debounce = true) applyActiveCriteria(debounce); } + private bool beatmapsSplitOut; + private void applyActiveCriteria(bool debounce, bool alwaysResetScrollPosition = true) { PendingFilter?.Cancel(); @@ -652,6 +678,13 @@ void perform() { PendingFilter = null; + if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut) + { + beatmapsSplitOut = activeCriteria.SplitOutDifficulties; + loadBeatmapSets(originalBeatmapSetsDetached); + return; + } + root.Filter(activeCriteria); itemsCache.Invalidate(); @@ -1055,7 +1088,7 @@ private class CarouselRoot : CarouselGroupEagerSelect // May only be null during construction (State.Value set causes PerformSelection to be triggered). private readonly BeatmapCarousel? carousel; - public readonly Dictionary BeatmapSetsByID = new Dictionary(); + public readonly Dictionary> BeatmapSetsByID = new Dictionary>(); public CarouselRoot(BeatmapCarousel carousel) { @@ -1069,20 +1102,25 @@ public CarouselRoot(BeatmapCarousel carousel) public override void AddItem(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; - BeatmapSetsByID.Add(set.BeatmapSet.ID, set); + if (BeatmapSetsByID.TryGetValue(set.BeatmapSet.ID, out var sets)) + sets.Add(set); + else + BeatmapSetsByID.Add(set.BeatmapSet.ID, new List { set }); base.AddItem(i); } - public CarouselBeatmapSet? RemoveChild(Guid beatmapSetID) + public IEnumerable RemoveChild(Guid beatmapSetID) { - if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSets)) { - RemoveItem(carouselBeatmapSet); - return carouselBeatmapSet; + foreach (var set in carouselBeatmapSets) + RemoveItem(set); + + return carouselBeatmapSets; } - return null; + return Enumerable.Empty(); } public override void RemoveItem(CarouselItem i) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index ab4f85fc92d4..a2ae1141267b 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -19,6 +19,11 @@ public class FilterCriteria public GroupMode Group; public SortMode Sort; + /// + /// Whether the display of beatmap sets should be split apart per-difficulty for the current criteria. + /// + public bool SplitOutDifficulties => Sort == SortMode.Difficulty; + public BeatmapSetInfo? SelectedBeatmapSet; public OptionalRange StarDifficulty; From 2b1c6ae612cb4e6cecbd6736748da942fa724e39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:38:23 +0900 Subject: [PATCH 040/214] Ensure ID is maintained in temporary `BeatmapSetInfo`s --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d1a9b4176b62..5157e37a3182 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -137,8 +137,10 @@ private void loadBeatmapSets(IEnumerable beatmapSets) { var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b => { - var set = new BeatmapSetInfo(new[] { b }); - return createCarouselSet(set); + return createCarouselSet(new BeatmapSetInfo(new[] { b }) + { + ID = b.BeatmapSet!.ID, + }); }).OfType(); newRoot.AddItems(carouselBeatmapSets); From ecbf0f138e2c7b3cf5716020a1aad4f8a834dfd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:38:43 +0900 Subject: [PATCH 041/214] Fix incorrect handling when new beatmaps arrive --- osu.Game/Screens/Select/BeatmapCarousel.cs | 35 ++++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5157e37a3182..2227eb801a48 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -393,7 +393,6 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; - var newSet = createCarouselSet(beatmapSet); var removedSets = root.RemoveChild(beatmapSet.ID); foreach (var removedSet in removedSets) @@ -405,13 +404,37 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => expirePanelImmediately(removedDrawable); } - if (newSet != null) + if (beatmapsSplitOut) { - root.AddItem(newSet); + foreach (var beatmap in beatmapSet.Beatmaps) + { + var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) + { + ID = beatmapSet.ID + }); - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + if (newSet != null) + { + root.AddItem(newSet); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + } + } + } + else + { + var newSet = createCarouselSet(beatmapSet); + + if (newSet != null) + { + root.AddItem(newSet); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + } } itemsCache.Invalidate(); From 018be4c20f408ee98bb00a3897e2658b4f016596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:40:34 +0900 Subject: [PATCH 042/214] Fix selection not being retained when switching between split mode --- osu.Game/Screens/Select/BeatmapCarousel.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2227eb801a48..c5e46a00b659 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,6 +131,12 @@ private void loadBeatmapSets(IEnumerable beatmapSets) { originalBeatmapSetsDetached = beatmapSets.Detach(); + if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) + selectedBeatmapSet = null; + + var selectedSetBefore = selectedBeatmapSet; + var selectedBetmapBefore = selectedBeatmap; + CarouselRoot newRoot = new CarouselRoot(this); if (beatmapsSplitOut) @@ -148,14 +154,12 @@ private void loadBeatmapSets(IEnumerable beatmapSets) else { var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType(); + newRoot.AddItems(carouselBeatmapSets); } root = newRoot; - if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) - selectedBeatmapSet = null; - Scroll.Clear(false); itemsCache.Invalidate(); ScrollToSelected(); @@ -164,6 +168,15 @@ private void loadBeatmapSets(IEnumerable beatmapSets) if (loadedTestBeatmaps) signalBeatmapsLoaded(); + + // Restore selection + if (selectedBetmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) + { + CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBetmapBefore.BeatmapInfo.ID); + + if (found != null) + found.State.Value = CarouselItemState.Selected; + } } private readonly List visibleItems = new List(); From 5555f73e97f22f9c6f0425bc4c9459bef88f3be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:38:18 +0900 Subject: [PATCH 043/214] Update test to match new behaviour --- .../SongSelect/TestSceneBeatmapCarousel.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 61f95dc628f5..b0aff8b4dbdf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -758,7 +758,7 @@ public void TestSortingStabilityWithNewItems() } [Test] - public void TestSortingWithFiltered() + public void TestSortingWithDifficultyFiltered() { List sets = new List(); @@ -777,13 +777,32 @@ public void TestSortingWithFiltered() loadBeatmaps(sets); + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + checkVisibleItemCount(false, 9); + checkVisibleItemCount(true, 1); + AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); - AddAssert("Check first set at end", () => carousel.BeatmapSets.First().Equals(sets.Last())); - AddAssert("Check last set at start", () => carousel.BeatmapSets.Last().Equals(sets.First())); + checkVisibleItemCount(false, 3); + checkVisibleItemCount(true, 1); + + AddUntilStep("Check all visible sets have one normal", () => + { + return carousel.Items.OfType() + .Where(p => p.IsPresent) + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == 3; + }); AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); - AddAssert("Check first set at start", () => carousel.BeatmapSets.First().Equals(sets.First())); - AddAssert("Check last set at end", () => carousel.BeatmapSets.Last().Equals(sets.Last())); + checkVisibleItemCount(false, 3); + checkVisibleItemCount(true, 1); + + AddUntilStep("Check all visible sets have one insane", () => + { + return carousel.Items.OfType() + .Where(p => p.IsPresent) + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Insane", StringComparison.Ordinal)) == 3; + }); } [Test] From a64381f8553dd49b9c5c5142aa2934c333b101a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:43:08 +0900 Subject: [PATCH 044/214] Add test coverage of add/remove when difficulties are split out --- .../SongSelect/TestSceneBeatmapCarousel.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index b0aff8b4dbdf..5af6d862b281 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -39,6 +39,7 @@ public partial class TestSceneBeatmapCarousel : OsuManualInputManagerTestScene private BeatmapInfo currentSelection => carousel.SelectedBeatmapInfo; private const int set_count = 5; + private const int diff_count = 3; [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -501,6 +502,36 @@ public void TestAddRemove() waitForSelection(set_count); } + [Test] + public void TestAddRemoveDifficultySort() + { + loadBeatmaps(); + + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + checkVisibleItemCount(false, set_count * diff_count); + + var firstAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + var secondAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + + AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded)); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded)); + + checkVisibleItemCount(false, (set_count + 2) * diff_count); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); + + checkVisibleItemCount(false, (set_count + 1) * diff_count); + + setSelected(set_count + 1, 1); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded)); + + checkVisibleItemCount(false, (set_count) * diff_count); + + waitForSelection(set_count); + } + [Test] public void TestSelectionEnteringFromEmptyRuleset() { @@ -662,7 +693,7 @@ public void TestSortingStabilityWithRemovedAndReaddedItem() for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); // only need to set the first as they are a shared reference. var beatmap = set.Beatmaps.First(); @@ -709,7 +740,7 @@ public void TestSortingStabilityWithNewItems() for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); // only need to set the first as they are a shared reference. var beatmap = set.Beatmaps.First(); @@ -768,7 +799,7 @@ public void TestSortingWithDifficultyFiltered() for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); set.Beatmaps[0].StarRating = 3 - i; set.Beatmaps[2].StarRating = 6 + i; sets.Add(set); @@ -857,7 +888,7 @@ public void TestHiding() AddStep("create hidden set", () => { - hidingSet = TestResources.CreateTestBeatmapSetInfo(3); + hidingSet = TestResources.CreateTestBeatmapSetInfo(diff_count); hidingSet.Beatmaps[1].Hidden = true; hiddenList.Clear(); @@ -904,7 +935,7 @@ public void TestSelectingFilteredRuleset() AddStep("add mixed ruleset beatmapset", () => { - testMixed = TestResources.CreateTestBeatmapSetInfo(3); + testMixed = TestResources.CreateTestBeatmapSetInfo(diff_count); for (int i = 0; i <= 2; i++) { @@ -926,7 +957,7 @@ public void TestSelectingFilteredRuleset() BeatmapSetInfo testSingle = null; AddStep("add single ruleset beatmapset", () => { - testSingle = TestResources.CreateTestBeatmapSetInfo(3); + testSingle = TestResources.CreateTestBeatmapSetInfo(diff_count); testSingle.Beatmaps.ForEach(b => { b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); @@ -949,7 +980,7 @@ public void TestCarouselRemembersSelection() manySets.Clear(); for (int i = 1; i <= 50; i++) - manySets.Add(TestResources.CreateTestBeatmapSetInfo(3)); + manySets.Add(TestResources.CreateTestBeatmapSetInfo(diff_count)); }); loadBeatmaps(manySets); @@ -1113,7 +1144,7 @@ private void loadBeatmaps(List beatmapSets = null, Func Date: Wed, 23 Aug 2023 19:44:39 +0900 Subject: [PATCH 045/214] Add coverage of selection retention when difficulties are split out --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 5af6d862b281..daa8c9c4c234 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1005,6 +1005,43 @@ public void TestCarouselRemembersSelection() AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1); } + [Test] + public void TestCarouselRemembersSelectionDifficultySort() + { + List manySets = new List(); + + AddStep("Populuate beatmap sets", () => + { + manySets.Clear(); + + for (int i = 1; i <= 50; i++) + manySets.Add(TestResources.CreateTestBeatmapSetInfo(diff_count)); + }); + + loadBeatmaps(manySets); + + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + advanceSelection(direction: 1, diff: false); + + for (int i = 0; i < 5; i++) + { + AddStep("Toggle non-matching filter", () => + { + carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false); + }); + + AddStep("Restore no filter", () => + { + carousel.Filter(new FilterCriteria(), false); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID); + }); + } + + // always returns to same selection as long as it's available. + AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1); + } + [Test] public void TestFilteringByUserStarDifficulty() { From 4881130caeccc728dbb789665fd161da1cfb0b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 03:32:12 +0900 Subject: [PATCH 046/214] Limit set/diff count in test to better fit on screen --- .../SongSelect/TestSceneBeatmapCarousel.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index daa8c9c4c234..d68fa6100c93 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -791,17 +791,20 @@ public void TestSortingStabilityWithNewItems() [Test] public void TestSortingWithDifficultyFiltered() { + const int local_diff_count = 3; + const int local_set_count = 2; + List sets = new List(); AddStep("Populuate beatmap sets", () => { sets.Clear(); - for (int i = 0; i < 3; i++) + for (int i = 0; i < local_set_count; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(diff_count); + var set = TestResources.CreateTestBeatmapSetInfo(local_diff_count); set.Beatmaps[0].StarRating = 3 - i; - set.Beatmaps[2].StarRating = 6 + i; + set.Beatmaps[1].StarRating = 6 + i; sets.Add(set); } }); @@ -810,29 +813,29 @@ public void TestSortingWithDifficultyFiltered() AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); - checkVisibleItemCount(false, 9); + checkVisibleItemCount(false, local_set_count * local_diff_count); checkVisibleItemCount(true, 1); AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); - checkVisibleItemCount(false, 3); + checkVisibleItemCount(false, local_set_count); checkVisibleItemCount(true, 1); AddUntilStep("Check all visible sets have one normal", () => { return carousel.Items.OfType() .Where(p => p.IsPresent) - .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == 3; + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == local_set_count; }); AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); - checkVisibleItemCount(false, 3); + checkVisibleItemCount(false, local_set_count); checkVisibleItemCount(true, 1); AddUntilStep("Check all visible sets have one insane", () => { return carousel.Items.OfType() .Where(p => p.IsPresent) - .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Insane", StringComparison.Ordinal)) == 3; + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Insane", StringComparison.Ordinal)) == local_set_count; }); } From c8f42f73e87029f3e235b92d3a97ba776d6327f9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Aug 2023 11:17:41 +0900 Subject: [PATCH 047/214] Adjust mania hit windows with gameplay rate --- .../Mods/IManiaRateAdjustmentMod.cs | 47 +++++++++++++++++++ .../Mods/ManiaModDaycore.cs | 5 +- .../Mods/ManiaModDoubleTime.cs | 5 +- .../Mods/ManiaModHalfTime.cs | 5 +- .../Mods/ManiaModNightcore.cs | 5 +- .../Scoring/ManiaHitWindows.cs | 20 ++++++++ 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs diff --git a/osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs b/osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs new file mode 100644 index 000000000000..ea01bd4436f1 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Mods +{ + /// + /// May be attached to rate-adjustment mods to adjust hit windows adjust relative to gameplay rate. + /// + /// + /// Historically, in osu!mania, hit windows are expected to adjust relative to the gameplay rate such that the real-world hit window remains the same. + /// + public interface IManiaRateAdjustmentMod : IApplicableToDifficulty, IApplicableToHitObject + { + BindableNumber SpeedChange { get; } + + HitWindows HitWindows { get; set; } + + void IApplicableToDifficulty.ApplyToDifficulty(BeatmapDifficulty difficulty) + { + HitWindows = new ManiaHitWindows(SpeedChange.Value); + HitWindows.SetDifficulty(difficulty.OverallDifficulty); + } + + void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject) + { + switch (hitObject) + { + case Note: + hitObject.HitWindows = HitWindows; + break; + + case HoldNote hold: + hold.Head.HitWindows = HitWindows; + hold.Tail.HitWindows = HitWindows; + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs index 309393b66418..dbe2a9a9fc67 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModDaycore : ModDaycore + public class ManiaModDaycore : ModDaycore, IManiaRateAdjustmentMod { + public HitWindows HitWindows { get; set; } = new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs index f4b9cf3b8840..a841a8ab3788 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModDoubleTime : ModDoubleTime + public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod { + public HitWindows HitWindows { get; set; } = new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs index 8d48e3acde3e..b0fbb1139659 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModHalfTime : ModHalfTime + public class ManiaModHalfTime : ModHalfTime, IManiaRateAdjustmentMod { + public HitWindows HitWindows { get; set; } = new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs index 748725af9fe4..f64f7ae31ad4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs @@ -2,11 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModNightcore : ModNightcore + public class ManiaModNightcore : ModNightcore, IManiaRateAdjustmentMod { + public HitWindows HitWindows { get; set; } = new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs index 289f8a00ef96..627f48f391ac 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs @@ -1,12 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Scoring { public class ManiaHitWindows : HitWindows { + private readonly double multiplier; + + public ManiaHitWindows() + : this(1) + { + } + + public ManiaHitWindows(double multiplier) + { + this.multiplier = multiplier; + } + public override bool IsHitResultAllowed(HitResult result) { switch (result) @@ -22,5 +35,12 @@ public override bool IsHitResultAllowed(HitResult result) return false; } + + protected override DifficultyRange[] GetRanges() => base.GetRanges().Select(r => + new DifficultyRange( + r.Result, + r.Min * multiplier, + r.Average * multiplier, + r.Max * multiplier)).ToArray(); } } From ba70d48d2cc0013fbc42338e0867cbe1e6ea8c82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 13:31:33 +0900 Subject: [PATCH 048/214] Fix one more test probably going off-screen --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index d68fa6100c93..8a38e2a84b04 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -112,7 +112,7 @@ public void TestExternalRulesetChange() [Test] public void TestScrollPositionMaintainedOnAdd() { - loadBeatmaps(count: 1, randomDifficulties: false); + loadBeatmaps(setCount: 1); for (int i = 0; i < 10; i++) { @@ -125,7 +125,7 @@ public void TestScrollPositionMaintainedOnAdd() [Test] public void TestDeletion() { - loadBeatmaps(count: 5, randomDifficulties: true); + loadBeatmaps(setCount: 5, randomDifficulties: true); AddStep("remove first set", () => carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item).OfType().First().BeatmapSet)); AddUntilStep("4 beatmap sets visible", () => this.ChildrenOfType().Count(set => set.Alpha > 0) == 4); @@ -134,7 +134,7 @@ public void TestDeletion() [Test] public void TestScrollPositionMaintainedOnDelete() { - loadBeatmaps(count: 50, randomDifficulties: false); + loadBeatmaps(setCount: 50); for (int i = 0; i < 10; i++) { @@ -151,7 +151,7 @@ public void TestScrollPositionMaintainedOnDelete() [Test] public void TestManyPanels() { - loadBeatmaps(count: 5000, randomDifficulties: true); + loadBeatmaps(setCount: 5000, randomDifficulties: true); } [Test] @@ -505,31 +505,34 @@ public void TestAddRemove() [Test] public void TestAddRemoveDifficultySort() { - loadBeatmaps(); + const int local_set_count = 1; + const int local_diff_count = 1; + + loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count); AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); - checkVisibleItemCount(false, set_count * diff_count); + checkVisibleItemCount(false, local_set_count * local_diff_count); - var firstAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); - var secondAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + var firstAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); + var secondAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded)); AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded)); - checkVisibleItemCount(false, (set_count + 2) * diff_count); + checkVisibleItemCount(false, (local_set_count + 2) * local_diff_count); AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); - checkVisibleItemCount(false, (set_count + 1) * diff_count); + checkVisibleItemCount(false, (local_set_count + 1) * local_diff_count); - setSelected(set_count + 1, 1); + setSelected(local_set_count + 1, 1); AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded)); - checkVisibleItemCount(false, (set_count) * diff_count); + checkVisibleItemCount(false, (local_set_count) * local_diff_count); - waitForSelection(set_count); + waitForSelection(local_set_count); } [Test] @@ -1171,8 +1174,8 @@ static RulesetInfo getRuleset(int index) } } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, - bool randomDifficulties = false) + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, + int? setCount = null, int? diffCount = null, bool randomDifficulties = false) { bool changed = false; @@ -1180,11 +1183,11 @@ private void loadBeatmaps(List beatmapSets = null, Func(); - for (int i = 1; i <= (count ?? set_count); i++) + for (int i = 1; i <= (setCount ?? set_count); i++) { beatmapSets.Add(randomDifficulties ? TestResources.CreateTestBeatmapSetInfo() - : TestResources.CreateTestBeatmapSetInfo(diff_count)); + : TestResources.CreateTestBeatmapSetInfo(diffCount ?? diff_count)); } } From a045cb71fc3d94f7d0d4f2bc0ab4ff770a1e0878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 19:44:22 +0200 Subject: [PATCH 049/214] Add assertions checking action type taken by legacy hit policy --- .../TestSceneObjectOrderedHitPolicy.cs | 35 +++++++++++++++++++ osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 8a38bf2b0886..afda805d2603 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -11,17 +11,20 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -83,6 +86,7 @@ public void TestClickSecondCircleBeforeFirstCircleTime() addJudgementAssert(hitObjects[1], HitResult.Miss); // note lock prevented the object from being hit, so the judgement offset should be very late. addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); + addClickActionAssert(0, ClickAction.Shake); } /// @@ -119,6 +123,7 @@ public void TestClickSecondCircleAtFirstCircleTime() addJudgementAssert(hitObjects[1], HitResult.Miss); // note lock prevented the object from being hit, so the judgement offset should be very late. addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); + addClickActionAssert(0, ClickAction.Shake); } /// @@ -155,6 +160,7 @@ public void TestClickSecondCircleAfterFirstCircleTime() addJudgementAssert(hitObjects[1], HitResult.Miss); // note lock prevented the object from being hit, so the judgement offset should be very late. addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); + addClickActionAssert(0, ClickAction.Shake); } /// @@ -192,6 +198,8 @@ public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() addJudgementAssert(hitObjects[1], HitResult.Meh); addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190 addJudgementOffsetAssert(hitObjects[0], -90); // time_second_circle - first_circle_time - 90 + addClickActionAssert(0, ClickAction.Hit); + addClickActionAssert(1, ClickAction.Hit); } /// @@ -229,6 +237,8 @@ public void TestClickSecondCircleAfterFirstCircleTimeWithFirstCircleJudged() addJudgementAssert(hitObjects[1], HitResult.Ok); addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190 addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time + addClickActionAssert(0, ClickAction.Hit); + addClickActionAssert(1, ClickAction.Hit); } /// @@ -271,6 +281,8 @@ public void TestHitCircleBeforeSliderHead() addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); + addClickActionAssert(0, ClickAction.Hit); + addClickActionAssert(1, ClickAction.Hit); } /// @@ -314,6 +326,8 @@ public void TestHitSliderTicksBeforeCircle() addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); + addClickActionAssert(0, ClickAction.Hit); + addClickActionAssert(1, ClickAction.Hit); } /// @@ -353,6 +367,7 @@ public void TestHitCircleBeforeSpinner() addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.Meh); + addClickActionAssert(0, ClickAction.Hit); } [Test] @@ -391,6 +406,9 @@ public void TestHitSliderHeadBeforeHitCircle() addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.Great); + addClickActionAssert(0, ClickAction.Shake); + addClickActionAssert(1, ClickAction.Hit); + addClickActionAssert(2, ClickAction.Hit); } private void addJudgementAssert(OsuHitObject hitObject, HitResult result) @@ -411,8 +429,12 @@ private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) () => judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, () => Is.EqualTo(offset).Within(100)); } + private void addClickActionAssert(int inputIndex, ClickAction action) + => AddAssert($"input #{inputIndex} resulted in {action}", () => testPolicy.ClickActions[inputIndex], () => Is.EqualTo(action)); + private ScoreAccessibleReplayPlayer currentPlayer = null!; private List judgementResults = null!; + private TestLegacyHitPolicy testPolicy = null!; private void performTest(List hitObjects, List frames, [CallerMemberName] string testCaseName = "") { @@ -513,6 +535,7 @@ private void performTest(List hitObjects, List frames AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddStep("Substitute hit policy", () => currentPlayer.ChildrenOfType().Single().HitPolicy = testPolicy = new TestLegacyHitPolicy()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } @@ -540,5 +563,17 @@ public ScoreAccessibleReplayPlayer(Score score) { } } + + private class TestLegacyHitPolicy : LegacyHitPolicy + { + public List ClickActions { get; } = new List(); + + public override ClickAction CheckHittable(DrawableHitObject hitObject, double time) + { + var action = base.CheckHittable(hitObject, time); + ClickActions.Add(action); + return action; + } + } } } diff --git a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs index c35d4a1b56db..6a740a6839ac 100644 --- a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs @@ -26,7 +26,7 @@ public void HandleHit(DrawableHitObject hitObject) { } - public ClickAction CheckHittable(DrawableHitObject hitObject, double time) + public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double time) { var aliveObjects = HitObjectContainer.AliveObjects.ToList(); int index = aliveObjects.IndexOf(hitObject); From 9ffc6cdd6110a94524c37b8d2c5bd3eaabb7e5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 19:58:31 +0200 Subject: [PATCH 050/214] Add test case covering overlapping slider fail case --- .../TestSceneObjectOrderedHitPolicy.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index afda805d2603..fbbac91af5e0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -411,6 +411,53 @@ public void TestHitSliderHeadBeforeHitCircle() addClickActionAssert(2, ClickAction.Hit); } + [Test] + public void TestOverlappingSliders() + { + const double time_first_slider = 1000; + const double time_second_slider = 1200; + Vector2 positionFirstSlider = new Vector2(100, 50); + Vector2 positionSecondSlider = new Vector2(100, 80); + var midpoint = (positionFirstSlider + positionSecondSlider) / 2; + + var hitObjects = new List + { + new Slider + { + StartTime = time_first_slider, + Position = positionFirstSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + }, + new Slider + { + StartTime = time_second_slider, + Position = positionSecondSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_slider, Position = midpoint, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_first_slider + 25, Position = midpoint, Actions = { OsuAction.LeftButton, OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_first_slider + 50, Position = midpoint }, + new OsuReplayFrame { Time = time_second_slider, Position = positionSecondSlider + new Vector2(0, 10), Actions = { OsuAction.LeftButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Ok); + addJudgementAssert(hitObjects[1], HitResult.Great); + addClickActionAssert(0, ClickAction.Hit); + addClickActionAssert(1, ClickAction.Hit); + } + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", From 48b3f7dced93f0600fdf22837c5b9c94308bf1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 20:34:36 +0200 Subject: [PATCH 051/214] Add test case covering stacks not shaking --- .../TestSceneObjectOrderedHitPolicy.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index fbbac91af5e0..738d1a073931 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -458,6 +458,26 @@ public void TestOverlappingSliders() addClickActionAssert(1, ClickAction.Hit); } + [Test] + public void TestStacksDoNotShake() + { + const double time_stack_start = 1000; + Vector2 position = new Vector2(80); + + var hitObjects = Enumerable.Range(0, 20).Select(i => new HitCircle + { + StartTime = time_stack_start + i * 100, + Position = position + }).Cast().ToList(); + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_stack_start - 450, Position = new Vector2(55), Actions = { OsuAction.LeftButton } }, + }); + + addClickActionAssert(0, ClickAction.Ignore); + } + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", From 64b36a73b10691c38889fb5ed530e7c08e53c0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Aug 2023 12:43:53 +0200 Subject: [PATCH 052/214] Rename test scene to match tested class --- ...eObjectOrderedHitPolicy.cs => TestSceneLegacyHitPolicy.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneObjectOrderedHitPolicy.cs => TestSceneLegacyHitPolicy.cs} (99%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs index 738d1a073931..6366a185f241 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests { - public partial class TestSceneObjectOrderedHitPolicy : RateAdjustedBeatmapTestScene + public partial class TestSceneLegacyHitPolicy : RateAdjustedBeatmapTestScene { private readonly OsuHitWindows referenceHitWindows; @@ -46,7 +46,7 @@ public partial class TestSceneObjectOrderedHitPolicy : RateAdjustedBeatmapTestSc /// private readonly string? exportLocation = null; - public TestSceneObjectOrderedHitPolicy() + public TestSceneLegacyHitPolicy() { referenceHitWindows = new OsuHitWindows(); referenceHitWindows.SetDifficulty(0); From ed2b1a07540fe4038b03424abe14582de495b661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Aug 2023 13:12:18 +0200 Subject: [PATCH 053/214] Contain entirety of legacy notelock badness inside the hit policy --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs | 4 ++-- .../Objects/Drawables/DrawableHitCircle.cs | 9 +++------ .../Objects/Drawables/DrawableOsuHitObject.cs | 3 ++- .../Objects/Drawables/DrawableSliderHead.cs | 2 +- osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs | 3 ++- osu.Game.Rulesets.Osu/UI/IHitPolicy.cs | 4 +++- osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs | 6 +++++- osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs | 5 +++-- 9 files changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index c818a361df35..af02087d1a57 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -131,7 +131,7 @@ public TestDrawableHitCircle(HitCircle h, bool auto, double hitOffset) protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) == ClickAction.Hit) + if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current, HitResult.Great) == ClickAction.Hit) { // force success ApplyResult(r => r.Type = HitResult.Great); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs index 6366a185f241..3accf4fad75e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs @@ -635,9 +635,9 @@ private class TestLegacyHitPolicy : LegacyHitPolicy { public List ClickActions { get; } = new List(); - public override ClickAction CheckHittable(DrawableHitObject hitObject, double time) + public override ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result) { - var action = base.CheckHittable(hitObject, time); + var action = base.CheckHittable(hitObject, time, result); ClickActions.Add(action); return action; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 09d818def889..932f6d3fffe2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -155,15 +155,12 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) } var result = ResultFor(timeOffset); - var clickAction = CheckHittable?.Invoke(this, Time.Current); + var clickAction = CheckHittable?.Invoke(this, Time.Current, result); - if (clickAction == ClickAction.Shake || (result == HitResult.None && clickAction != ClickAction.Ignore)) - { + if (clickAction == ClickAction.Shake) Shake(); - return; - } - if (result == HitResult.None) + if (result == HitResult.None || clickAction != ClickAction.Hit) return; ApplyResult(r => diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a8ce2118c83c..920dfcab035b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -37,7 +38,7 @@ public abstract partial class DrawableOsuHitObject : DrawableHitObject, and this hit object will be shaken for return values of /// . /// - public Func CheckHittable; + public Func CheckHittable; protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index a4cf69ee3159..41f6a40c0ae8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -61,7 +61,7 @@ protected override void OnApply() pathVersion.BindTo(DrawableSlider.PathVersion); - CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? ClickAction.Hit; + CheckHittable = (d, t, r) => DrawableSlider.CheckHittable?.Invoke(d, t, r) ?? ClickAction.Hit; } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs index 7503c43e0b85..69bd360b5ef7 100644 --- a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs @@ -4,6 +4,7 @@ #nullable disable using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI @@ -15,7 +16,7 @@ public class AnyOrderHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } - public ClickAction CheckHittable(DrawableHitObject hitObject, double time) => ClickAction.Hit; + public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result) => ClickAction.Hit; public void HandleHit(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs index 9820b8c1886d..44d3b3740827 100644 --- a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs @@ -3,6 +3,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI @@ -19,8 +20,9 @@ public interface IHitPolicy /// /// The to check. /// The time to check. + /// The result that the object would be judged with if hit. /// Whether can be hit at the given . - ClickAction CheckHittable(DrawableHitObject hitObject, double time); + ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result); /// /// Handles a being hit. diff --git a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs index 6a740a6839ac..52602153457e 100644 --- a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI @@ -26,7 +27,7 @@ public void HandleHit(DrawableHitObject hitObject) { } - public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double time) + public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result) { var aliveObjects = HitObjectContainer.AliveObjects.ToList(); int index = aliveObjects.IndexOf(hitObject); @@ -38,6 +39,9 @@ public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double tim return ClickAction.Ignore; } + if (result == HitResult.None) + return ClickAction.Shake; + foreach (DrawableHitObject testObject in aliveObjects) { if (testObject.AllJudged) diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index f33ca58aeff2..6fd0b512be19 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI @@ -24,7 +25,7 @@ public class StartTimeOrderedHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } - public ClickAction CheckHittable(DrawableHitObject hitObject, double time) + public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult _) { DrawableHitObject blockingObject = null; @@ -51,7 +52,7 @@ public void HandleHit(DrawableHitObject hitObject) if (!hitObjectCanBlockFutureHits(hitObject)) return; - if (CheckHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset) != ClickAction.Hit) + if (CheckHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset, hitObject.Result.Type) != ClickAction.Hit) throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); // Miss all hitobjects prior to the hit one. From a3160364603211a7dd1d06e1996c8603d5c700df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Aug 2023 13:20:38 +0200 Subject: [PATCH 054/214] Add failing test case for special autopilot hittable range --- .../TestSceneLegacyHitPolicy.cs | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs index 3accf4fad75e..cc5c27a62c69 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -478,6 +479,32 @@ public void TestStacksDoNotShake() addClickActionAssert(0, ClickAction.Ignore); } + [Test] + public void TestAutopilotReducesHittableRange() + { + const double time_circle = 1500; + Vector2 positionCircle = Vector2.Zero; + + var hitObjects = new List + { + new HitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_circle - 250, Position = positionCircle, Actions = { OsuAction.LeftButton } } + }, new Mod[] { new OsuModAutopilot() }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + // note lock prevented the object from being hit, so the judgement offset should be very late. + addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); + addClickActionAssert(0, ClickAction.Shake); + } + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", @@ -503,11 +530,20 @@ private void addClickActionAssert(int inputIndex, ClickAction action) private List judgementResults = null!; private TestLegacyHitPolicy testPolicy = null!; - private void performTest(List hitObjects, List frames, [CallerMemberName] string testCaseName = "") + private void performTest(List hitObjects, List frames, IEnumerable? extraMods = null, [CallerMemberName] string testCaseName = "") { + List mods = null!; IBeatmap playableBeatmap = null!; Score score = null!; + AddStep("set up mods", () => + { + mods = new List { new OsuModClassic() }; + + if (extraMods != null) + mods.AddRange(extraMods); + }); + AddStep("create beatmap", () => { var cpi = new ControlPointInfo(); @@ -550,7 +586,8 @@ private void performTest(List hitObjects, List frames ScoreInfo = { Ruleset = new OsuRuleset().RulesetInfo, - BeatmapInfo = playableBeatmap.BeatmapInfo + BeatmapInfo = playableBeatmap.BeatmapInfo, + Mods = mods.ToArray() } }; }); @@ -584,7 +621,7 @@ private void performTest(List hitObjects, List frames AddStep("load player", () => { - SelectedMods.Value = new[] { new OsuModClassic() }; + SelectedMods.Value = mods.ToArray(); var p = new ScoreAccessibleReplayPlayer(score); From 7f215f163ffc307ce9060e9d32f572162996e5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Aug 2023 13:37:39 +0200 Subject: [PATCH 055/214] Use reduced hittable range with autopilot active --- .../TestSceneLegacyHitPolicy.cs | 16 ++++++++++++++-- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 6 +++++- osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs | 14 +++++++++----- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs index cc5c27a62c69..b4ab53a658af 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs @@ -639,7 +639,12 @@ private void performTest(List hitObjects, List frames AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); - AddStep("Substitute hit policy", () => currentPlayer.ChildrenOfType().Single().HitPolicy = testPolicy = new TestLegacyHitPolicy()); + AddStep("Substitute hit policy", () => + { + var playfield = currentPlayer.ChildrenOfType().Single(); + var currentPolicy = playfield.HitPolicy; + playfield.HitPolicy = testPolicy = new TestLegacyHitPolicy(currentPolicy); + }); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } @@ -670,11 +675,18 @@ public ScoreAccessibleReplayPlayer(Score score) private class TestLegacyHitPolicy : LegacyHitPolicy { + private readonly IHitPolicy currentPolicy; + + public TestLegacyHitPolicy(IHitPolicy currentPolicy) + { + this.currentPolicy = currentPolicy; + } + public List ClickActions { get; } = new List(); public override ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result) { - var action = base.CheckHittable(hitObject, time, result); + var action = currentPolicy.CheckHittable(hitObject, time, result); ClickActions.Add(action); return action; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 229f80c2bd86..82deec41f541 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -57,7 +58,10 @@ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset var osuRuleset = (DrawableOsuRuleset)drawableRuleset; if (ClassicNoteLock.Value) - osuRuleset.Playfield.HitPolicy = new LegacyHitPolicy(); + { + double hittableRange = OsuHitWindows.MISS_WINDOW - (drawableRuleset.Mods.OfType().Any() ? 200 : 0); + osuRuleset.Playfield.HitPolicy = new LegacyHitPolicy(hittableRange); + } usingHiddenFading = drawableRuleset.Mods.OfType().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false; } diff --git a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs index 52602153457e..2b6359fb4661 100644 --- a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -23,6 +24,13 @@ public class LegacyHitPolicy : IHitPolicy { public IHitObjectContainer HitObjectContainer { get; set; } + private readonly double hittableRange; + + public LegacyHitPolicy(double hittableRange = OsuHitWindows.MISS_WINDOW) + { + this.hittableRange = hittableRange; + } + public void HandleHit(DrawableHitObject hitObject) { } @@ -57,11 +65,7 @@ public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double tim return ClickAction.Shake; } - // stable has `const HitObjectManager.HITTABLE_RANGE = 400;`, which is only used for notelock code. - // probably not a coincidence that this is equivalent to lazer's OsuHitWindows.MISS_WINDOW. - - // TODO stable compares to 200 when autopilot is enabled, instead of 400. - return Math.Abs(hitObject.HitObject.StartTime - time) < 400 ? ClickAction.Hit : ClickAction.Shake; + return Math.Abs(hitObject.HitObject.StartTime - time) < hittableRange ? ClickAction.Hit : ClickAction.Shake; } } } From 38e34593911801c0f31a8ca787b142c18f54b9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 24 Aug 2023 08:54:38 +0200 Subject: [PATCH 056/214] Fix broken assertion --- osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs index b4ab53a658af..2cfbe6611f37 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs @@ -198,7 +198,7 @@ public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() addJudgementAssert(hitObjects[0], HitResult.Meh); addJudgementAssert(hitObjects[1], HitResult.Meh); addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190 - addJudgementOffsetAssert(hitObjects[0], -90); // time_second_circle - first_circle_time - 90 + addJudgementOffsetAssert(hitObjects[1], -190); // time_second_circle - first_circle_time - 90 addClickActionAssert(0, ClickAction.Hit); addClickActionAssert(1, ClickAction.Hit); } @@ -520,7 +520,7 @@ private void addJudgementAssert(string name, Func hitObject, HitR private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", - () => judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, () => Is.EqualTo(offset).Within(100)); + () => judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, () => Is.EqualTo(offset).Within(50)); } private void addClickActionAssert(int inputIndex, ClickAction action) From 2e27a476bb3c4b90d08384cfda3da7e60774e278 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 24 Aug 2023 18:04:47 +0900 Subject: [PATCH 057/214] Re-enable sample playback for `PasswordEntryPopover` and remove `sampleJoin` playback instead --- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 11 +++-------- .../Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 5 ----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 2aa42d0d5055..551468beb954 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -31,8 +31,6 @@ public partial class OsuPopover : Popover, IKeyBindingHandler // required due to LoadAsyncComplete() calling PopOut() during load - similar workaround to `OsuDropdownMenu` private bool wasOpened; - protected virtual bool PlayPopInOutSamples => true; - public OsuPopover(bool withPadding = true) { Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); @@ -64,11 +62,8 @@ protected override void PopIn() this.ScaleTo(1, scale_duration, Easing.OutElasticHalf); this.FadeIn(fade_duration, Easing.OutQuint); - if (PlayPopInOutSamples) - { - samplePopIn?.Play(); - wasOpened = true; - } + samplePopIn?.Play(); + wasOpened = true; } protected override void PopOut() @@ -76,7 +71,7 @@ protected override void PopOut() this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint); - if (wasOpened && PlayPopInOutSamples) + if (wasOpened) samplePopOut?.Play(); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index a45583a2ece4..5cf2f91ff45d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -170,7 +170,6 @@ protected override bool OnClick(ClickEvent e) if (Room.HasPassword.Value) { - sampleJoin?.Play(); this.ShowPopover(); return true; } @@ -191,10 +190,6 @@ public partial class PasswordEntryPopover : OsuPopover protected override bool BlockNonPositionalInput => true; - // When a room is clicked, it already plays a click sound, which clashes pretty badly with the pop in sound. - // Dunno about this one. I'd probably remove the click sound from the panel in cases they are password protected and play these pop in / out sounds. - protected override bool PlayPopInOutSamples => false; - public PasswordEntryPopover(Room room) { this.room = room; From 7ef5a71e91a56a3e8340393376a97973ceb3e266 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 24 Aug 2023 18:13:23 +0900 Subject: [PATCH 058/214] Move PopIn/PopOut sample playback from `WaveOverlayContainer` to `WaveContainer` (so Multiplayer/Lounge plays the samples) --- osu.Game/Graphics/Containers/WaveContainer.cs | 22 +++++++++++++++++++ osu.Game/Overlays/WaveOverlayContainer.cs | 5 +++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 9fd3d103c9ca..0b1d44ccba55 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -2,6 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -32,6 +35,13 @@ public partial class WaveContainer : VisibilityContainer protected override bool StartHidden => true; + private Sample? samplePopIn; + private Sample? samplePopOut; + protected virtual string PopInSampleName => "UI/wave-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-big-pop-out"; + + private bool wasShown = false; + public Color4 FirstWaveColour { get => firstWave.Colour; @@ -56,6 +66,13 @@ public Color4 FourthWaveColour set => fourthWave.Colour = value; } + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio) + { + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); + } + public WaveContainer() { Masking = true; @@ -110,6 +127,8 @@ protected override void PopIn() w.Show(); contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); + samplePopIn?.Play(); + wasShown = true; } protected override void PopOut() @@ -118,6 +137,9 @@ protected override void PopOut() w.Hide(); contentContainer.MoveToY(2, DISAPPEAR_DURATION, Easing.In); + + if (wasShown) + samplePopOut?.Play(); } protected override void UpdateAfterChildren() diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 848d9e60b7f2..b0ddef5c2b97 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -18,8 +18,9 @@ public abstract partial class WaveOverlayContainer : OsuFocusedOverlayContainer protected override bool StartHidden => true; - protected override string PopInSampleName => "UI/wave-pop-in"; - protected override string PopOutSampleName => "UI/overlay-big-pop-out"; + // `WaveContainer` plays PopIn/PopOut samples, so we disable the overlay-level one as to not double-up sample playback. + protected override string PopInSampleName => ""; + protected override string PopOutSampleName => ""; public const float HORIZONTAL_PADDING = 50; From f4415a5bab9aec67ea77af89c794921dae6a8c4e Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 24 Aug 2023 18:20:36 +0900 Subject: [PATCH 059/214] Add more detail to comment --- osu.Game/Graphics/Containers/WaveContainer.cs | 3 ++- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 0b1d44ccba55..09d1f7e9a4b7 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -40,7 +40,8 @@ public partial class WaveContainer : VisibilityContainer protected virtual string PopInSampleName => "UI/wave-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-big-pop-out"; - private bool wasShown = false; + // required due to LoadAsyncComplete() in `VisibilityContainer` calling PopOut() during load - similar workaround to `OsuDropdownMenu` + private bool wasShown; public Color4 FirstWaveColour { diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 551468beb954..9b4689958ca6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -28,7 +28,7 @@ public partial class OsuPopover : Popover, IKeyBindingHandler protected virtual string PopInSampleName => "UI/overlay-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-pop-out"; - // required due to LoadAsyncComplete() calling PopOut() during load - similar workaround to `OsuDropdownMenu` + // required due to LoadAsyncComplete() in `VisibilityContainer` calling PopOut() during load - similar workaround to `OsuDropdownMenu` private bool wasOpened; public OsuPopover(bool withPadding = true) From b471ab07a62dc225275154efdfd15c46646f0104 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 18:30:59 +0900 Subject: [PATCH 060/214] Fix typo in test step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 8a38e2a84b04..110466f6d54a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1016,7 +1016,7 @@ public void TestCarouselRemembersSelectionDifficultySort() { List manySets = new List(); - AddStep("Populuate beatmap sets", () => + AddStep("Populate beatmap sets", () => { manySets.Clear(); From 9e94f3809103b6079a158e19050f9bc15829235a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 18:33:15 +0900 Subject: [PATCH 061/214] Fix typo in local variable --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c5e46a00b659..91d1ced9d9d1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -135,7 +135,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) selectedBeatmapSet = null; var selectedSetBefore = selectedBeatmapSet; - var selectedBetmapBefore = selectedBeatmap; + var selectedBeatmapBefore = selectedBeatmap; CarouselRoot newRoot = new CarouselRoot(this); @@ -170,9 +170,9 @@ private void loadBeatmapSets(IEnumerable beatmapSets) signalBeatmapsLoaded(); // Restore selection - if (selectedBetmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) + if (selectedBeatmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) { - CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBetmapBefore.BeatmapInfo.ID); + CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBeatmapBefore.BeatmapInfo.ID); if (found != null) found.State.Value = CarouselItemState.Selected; From 89eeff515b42a9724ffd2e552f9e7d462f848d5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 00:52:54 +0900 Subject: [PATCH 062/214] Reduce complexity of selection restore --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 91d1ced9d9d1..d52b5592f855 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -134,8 +134,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; - var selectedSetBefore = selectedBeatmapSet; - var selectedBeatmapBefore = selectedBeatmap; + var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo; CarouselRoot newRoot = new CarouselRoot(this); @@ -170,9 +169,9 @@ private void loadBeatmapSets(IEnumerable beatmapSets) signalBeatmapsLoaded(); // Restore selection - if (selectedBeatmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) + if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates)) { - CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBeatmapBefore.BeatmapInfo.ID); + CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBeatmapBefore.ID); if (found != null) found.State.Value = CarouselItemState.Selected; From 35cdd6d8661b2b4df2735e4128c3334fd014670f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 01:07:07 +0900 Subject: [PATCH 063/214] Use `string.Empty` --- osu.Game/Overlays/WaveOverlayContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index b0ddef5c2b97..0295ff467ae6 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -19,8 +19,8 @@ public abstract partial class WaveOverlayContainer : OsuFocusedOverlayContainer protected override bool StartHidden => true; // `WaveContainer` plays PopIn/PopOut samples, so we disable the overlay-level one as to not double-up sample playback. - protected override string PopInSampleName => ""; - protected override string PopOutSampleName => ""; + protected override string PopInSampleName => string.Empty; + protected override string PopOutSampleName => string.Empty; public const float HORIZONTAL_PADDING = 50; From bf0f4fddad3c2c5e6a54b41333fea68daef82b2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 01:08:22 +0900 Subject: [PATCH 064/214] Localise non-overridden samples --- osu.Game/Graphics/Containers/WaveContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 09d1f7e9a4b7..e84cb276a43c 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -37,8 +37,6 @@ public partial class WaveContainer : VisibilityContainer private Sample? samplePopIn; private Sample? samplePopOut; - protected virtual string PopInSampleName => "UI/wave-pop-in"; - protected virtual string PopOutSampleName => "UI/overlay-big-pop-out"; // required due to LoadAsyncComplete() in `VisibilityContainer` calling PopOut() during load - similar workaround to `OsuDropdownMenu` private bool wasShown; @@ -70,8 +68,8 @@ public Color4 FourthWaveColour [BackgroundDependencyLoader(true)] private void load(AudioManager audio) { - samplePopIn = audio.Samples.Get(PopInSampleName); - samplePopOut = audio.Samples.Get(PopOutSampleName); + samplePopIn = audio.Samples.Get("UI/wave-pop-in"); + samplePopOut = audio.Samples.Get("UI/overlay-big-pop-out"); } public WaveContainer() From 4fd165c0a7dc6b9c49cb0e675de6084ed47fbdc0 Mon Sep 17 00:00:00 2001 From: Dreamurrrr <21233238+Dreamurrrr@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:28:17 -0500 Subject: [PATCH 065/214] Update README.md to include VS Code plugin prerequisites --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf7ce357917b..792e2d646a35 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Please make sure you have the following prerequisites: - A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed. -When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) plugin installed. ### Downloading the source code From 84f4fab9cfad4a122a271977ed7902f9e504904b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 18:09:51 +0900 Subject: [PATCH 066/214] Adjust test to actually test diff splitting --- .../SongSelect/TestSceneBeatmapCarousel.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 110466f6d54a..040b3415843e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -505,8 +505,8 @@ public void TestAddRemove() [Test] public void TestAddRemoveDifficultySort() { - const int local_set_count = 1; - const int local_diff_count = 1; + const int local_set_count = 2; + const int local_diff_count = 2; loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count); @@ -515,23 +515,17 @@ public void TestAddRemoveDifficultySort() checkVisibleItemCount(false, local_set_count * local_diff_count); var firstAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); - var secondAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded)); - AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded)); - - checkVisibleItemCount(false, (local_set_count + 2) * local_diff_count); - - AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); checkVisibleItemCount(false, (local_set_count + 1) * local_diff_count); - setSelected(local_set_count + 1, 1); - - AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded)); + AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); checkVisibleItemCount(false, (local_set_count) * local_diff_count); + setSelected(local_set_count, 1); + waitForSelection(local_set_count); } From 10b14501380320939f43fe715b033970b8af4a2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 18:10:54 +0900 Subject: [PATCH 067/214] Rename remove method to better explain return type being `IEnumerable` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d52b5592f855..a8da5dc742c7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -405,7 +405,7 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; - var removedSets = root.RemoveChild(beatmapSet.ID); + var removedSets = root.RemoveItemsByID(beatmapSet.ID); foreach (var removedSet in removedSets) { @@ -1147,7 +1147,7 @@ public override void AddItem(CarouselItem i) base.AddItem(i); } - public IEnumerable RemoveChild(Guid beatmapSetID) + public IEnumerable RemoveItemsByID(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSets)) { From 8a1fc7c340070e1f04d5ffca1488cfde70a054be Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 26 Aug 2023 01:20:41 +0300 Subject: [PATCH 068/214] Basic stuff (not working for now) --- osu.Game/Overlays/Mods/ModMapInfoDisplay.cs | 203 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 18 ++ osu.Game/Screens/Select/BeatmapDetails.cs | 6 + .../Screens/Select/Details/AdvancedStats.cs | 21 ++ osu.Game/Screens/Select/SongSelect.cs | 3 + 5 files changed, 251 insertions(+) create mode 100644 osu.Game/Overlays/Mods/ModMapInfoDisplay.cs diff --git a/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs b/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs new file mode 100644 index 000000000000..20666391d66b --- /dev/null +++ b/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs @@ -0,0 +1,203 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Select.Details; +using osu.Game.Localisation; +using osuTK; + + +namespace osu.Game.Overlays.Mods +{ + public partial class ModMapInfoDisplay : Container, IHasCurrentValue + { + public const float HEIGHT = 42; + private const float transition_duration = 200; + + private readonly Box contentBackground; + private readonly Box labelBackground; + private readonly FillFlowContainer content; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + /// + /// Text to display in the left area of the display. + /// + //protected abstract LocalisableString Label { get; } + protected LocalisableString Label => CommonStrings.Finish; + //protected string Label { get; } + + protected virtual float ValueAreaWidth => 56; + + protected virtual string CounterFormat => @"N0"; + + protected override Container Content => content; + + protected readonly RollingCounter Counter; + + public ModMapInfoDisplay() + { + Height = HEIGHT; + AutoSizeAxes = Axes.X; + + InternalChild = new InputBlockingContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), + Children = new Drawable[] + { + contentBackground = new Box + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Width = ValueAreaWidth + ModSelectPanel.CORNER_RADIUS + }, + new GridContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, ValueAreaWidth) + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + Children = new Drawable[] + { + labelBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 18 }, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Text = Label, + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + } + } + }, + content = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Spacing = new Vector2(2, 0), + Child = Counter = new EffectCounter(CounterFormat) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = { BindTarget = Current.Value.StarRating } + } + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + labelBackground.Colour = colourProvider.Background4; + } + + protected override void LoadComplete() + { + Current.BindValueChanged(e => + { + //var effect = CalculateEffectForComparison(e.NewValue.CompareTo(Current.Default)); + setColours(e.NewValue.StarRating.Value); + }, true); + } + + /// + /// Fades colours of text and its background according to displayed value. + /// + /// random number. + private void setColours(double stars) + { + contentBackground.FadeColour(colours.ForStarDifficulty(stars), transition_duration, Easing.OutQuint); + } + + /// + /// Converts signed integer into . Negative values are counted as difficulty reduction, positive as increase. + /// + /// Value to convert. Will arrive from comparison between bindable once it changes and it's . + /// Effect of the value. + protected virtual ModEffect CalculateEffectForComparison(int comparison) + { + if (comparison == 0) + return ModEffect.NotChanged; + if (comparison < 0) + return ModEffect.DifficultyReduction; + + return ModEffect.DifficultyIncrease; + } + + protected enum ModEffect + { + NotChanged, + DifficultyReduction, + DifficultyIncrease + } + + private partial class EffectCounter : RollingCounter + { + private readonly string? format; + + public EffectCounter(string? format) + { + this.format = format; + } + + protected override double RollingDuration => 500; + + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(format); + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9e92e9d959f4..ef5f6cf32351 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -25,6 +25,7 @@ using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Details; using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -123,6 +124,7 @@ protected virtual IEnumerable CreateFooterButtons() private Container aboveColumnsContent = null!; private DifficultyMultiplierDisplay? multiplierDisplay; + private ModMapInfoDisplay mapInfoDisplay = null!; protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } @@ -219,6 +221,12 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio) }); } + aboveColumnsContent.Add(mapInfoDisplay = new ModMapInfoDisplay + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft + }); + FooterContent.Child = footerButtonFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -244,6 +252,10 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio) globalAvailableMods.BindTo(game.AvailableMods); } + public void SetBindedMapStats(Bindable stats) + { + mapInfoDisplay.Current = stats; + } public override void Hide() { base.Hide(); @@ -399,6 +411,12 @@ private void updateMultiplier() multiplierDisplay.Current.Value = multiplier; } + private void updateMapInfo() + { + if (mapInfoDisplay == null) + return; + } + private void updateCustomisation() { if (CustomisationButton == null) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 712b6105153d..c56411bef555 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -273,6 +274,11 @@ private void updateMetrics() loading.Hide(); } + public Bindable GetBindedAdjustedMapStats() + { + return advanced.AdjustedMapStats.GetBoundCopy(); + } + private partial class DetailBox : Container { private readonly Container content; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index a383298faa24..016e4ff2df0b 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -113,6 +113,8 @@ private void modsChanged(ValueChangedEvent> mods) updateStatistics(); } + public Bindable AdjustedMapStats = new Bindable(); + private void updateStatistics() { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; @@ -146,6 +148,14 @@ private void updateStatistics() ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); updateStarDifficulty(); + + var temp = AdjustedMapStats.Value; + temp.CS.Value = FirstValue.Value.adjustedValue ?? 0; + temp.HP.Value = HpDrain.Value.adjustedValue ?? 0; + temp.OD.Value = Accuracy.Value.adjustedValue ?? 0; + temp.AR.Value = ApproachRate.Value.adjustedValue ?? 5; + AdjustedMapStats.Value = temp; + } private CancellationTokenSource starDifficultyCancellationSource; @@ -178,6 +188,11 @@ private void updateStarDifficulty() => Scheduler.AddOnce(() => return; starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); + + var temp = AdjustedMapStats.Value; + temp.StarRating.Value = moddedDifficulty.Value.Stars; + AdjustedMapStats.Value = temp; + }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); }); @@ -296,4 +311,10 @@ public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) } } } + public struct MapStats + { + public Bindable StarRating; + public Bindable MinBPM, MaxBPM, AvgBPM; + public Bindable CS, HP, AR, OD; + } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 58755878d0d5..17b1b1f870f1 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -305,6 +305,9 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog // therein it will be registered at the `OsuGame` level to properly function as a blocking overlay. LoadComponent(ModSelect = CreateModSelectOverlay()); + var bindedStats = BeatmapDetails.Details.GetBindedAdjustedMapStats(); + ModSelect.SetBindedMapStats(bindedStats); + if (Footer != null) { foreach (var (button, overlay) in CreateFooterButtons()) From 3a6920c3067f90c53578d021f3ecf7e89d368c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 09:51:37 +0200 Subject: [PATCH 069/214] Add failing test coverage of beatmap update flow w/ split diffs --- .../TestSceneUpdateBeatmapSetButton.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 11d55bc0bd23..585d93d3afb8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays.Dialog; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; +using osu.Game.Screens.Select.Filter; using osu.Game.Tests.Online; using osu.Game.Tests.Resources; using osuTK.Input; @@ -192,6 +193,57 @@ public void TestUpdateLocalBeatmap() AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); } + [Test] + public void TestSplitDisplay() + { + ArchiveDownloadRequest? downloadRequest = null; + + AddStep("set difficulty sort mode", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty })); + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("multiple \"sets\" visible", () => carousel.ChildrenOfType().Count(), () => Is.GreaterThan(1)); + AddUntilStep("update button visible", getUpdateButton, () => Is.Not.Null); + + AddStep("click button", () => getUpdateButton()?.TriggerClick()); + + AddUntilStep("wait for download started", () => + { + downloadRequest = beatmapDownloader.GetExistingDownload(testBeatmapSetInfo); + return downloadRequest != null; + }); + + AddUntilStep("wait for button disabled", () => getUpdateButton()?.Enabled.Value == false); + + AddUntilStep("progress download to completion", () => + { + if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + { + testRequest.SetProgress(testRequest.Progress + 0.1f); + + if (testRequest.Progress >= 1) + { + testRequest.TriggerSuccess(); + + // usually this would be done by the import process. + testBeatmapSetInfo.Beatmaps.First().MD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + // usually this would be done by a realm subscription. + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + return true; + } + } + + return false; + }); + } + private BeatmapCarousel createCarousel() { return carousel = new BeatmapCarousel From 6251803868588f188ca7b78f74ad4c9e18d4b10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 10:02:05 +0200 Subject: [PATCH 070/214] Add failing test coverage of selection not being retained on song select --- .../TestSceneBeatmapEditorNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 54ee1659e169..d0fa5fc73760 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -9,6 +9,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; @@ -16,6 +17,7 @@ using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; using osu.Game.Tests.Resources; using osuTK.Input; @@ -203,6 +205,33 @@ public void TestAttemptGlobalMusicOperationFromEditor() AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying); } + [TestCase(SortMode.Title)] + [TestCase(SortMode.Difficulty)] + public void TestSelectionRetainedOnExit(SortMode sortMode) + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep($"set sort mode to {sortMode}", () => Game.LocalConfig.SetValue(OsuSetting.SongSelectSortingMode, sortMode)); + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddStep("exit editor", () => InputManager.Key(Key.Escape)); + AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor); + + AddUntilStep("selection retained on song select", + () => Game.Beatmap.Value.BeatmapInfo.ID, + () => Is.EqualTo(beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0).ID)); + } + private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; From 0af6cc1394f7fe521bdf94773b423b5ff61c2bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 10:02:30 +0200 Subject: [PATCH 071/214] Fix online ID not being propagated in split difficulty mode Would result in failures to re-download the beatmap in update flows, for instance. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a8da5dc742c7..0354e7280333 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -145,6 +145,7 @@ private void loadBeatmapSets(IEnumerable beatmapSets) return createCarouselSet(new BeatmapSetInfo(new[] { b }) { ID = b.BeatmapSet!.ID, + OnlineID = b.BeatmapSet!.OnlineID }); }).OfType(); @@ -422,7 +423,8 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) { - ID = beatmapSet.ID + ID = beatmapSet.ID, + OnlineID = beatmapSet.OnlineID }); if (newSet != null) From 80ec18d117fc7ae252731bcde4e65a71be838ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 10:06:26 +0200 Subject: [PATCH 072/214] Fix incorrect selection restore code in split case The fallback to "any of the added sets" needs to be applied after they've all been added, rather than with every added one. Otherwise, in flows that expect a particular difficulty to be selected in the end (such as exiting from editor) would end up switching away from the edited beatmap. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0354e7280333..861385f45307 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -419,6 +419,8 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => if (beatmapsSplitOut) { + var newSets = new List(); + foreach (var beatmap in beatmapSet.Beatmaps) { var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) @@ -429,13 +431,18 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => if (newSet != null) { + newSets.Add(newSet); root.AddItem(newSet); - - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); } } + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + { + var toSelect = newSets.FirstOrDefault(s => s.Beatmaps.Any(b => b.BeatmapInfo.ID == previouslySelectedID)) + ?? newSets.FirstOrDefault(); + select(toSelect); + } } else { From b9795eb3d4e09d66c9e2950da6e8742637dd8dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 11:02:22 +0200 Subject: [PATCH 073/214] Fix changes to beatmap sets being undone on switching sort mode --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 861385f45307..9ea94b2a52fa 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -78,7 +78,7 @@ public partial class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler originalBeatmapSetsDetached = Enumerable.Empty(); + private List originalBeatmapSetsDetached = new List(); /// /// Raised when the is changed. @@ -381,6 +381,8 @@ private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets)) return; + originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSetID); + foreach (var set in existingSets) { foreach (var beatmap in set.Beatmaps) @@ -402,6 +404,9 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { Guid? previouslySelectedID = null; + originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID); + originalBeatmapSetsDetached.Add(beatmapSet.Detach()); + // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; From ba1f6439bf0dc3d5d283c5cf32ce35d8b93acebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 12:44:57 +0200 Subject: [PATCH 074/214] Disable redundant verbatim string prefix inspections For some reason this started flaring up recently all over for me and showing inspections all over, which are _technically_ valid, but interfere with our convention of using verbatim string prefixes to denote non-localisable strings. This, as a result, led to circular inspections (addressing the r# inspection results in getting the osu-localisation-analyser one, addresssing that one results in getting the r# inspection back, etc. ad nauseam). --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index ef39c12768f3..c2778ca5b1d8 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -170,7 +170,7 @@ ERROR WARNING WARNING - HINT + DO_NOT_SHOW WARNING WARNING WARNING From 99d5ff9efbd7b7197497acd53f757345dc1666b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 13:50:52 +0200 Subject: [PATCH 075/214] Switch download requests to new API endpoint This API endpoint is intended for usage with the entire `solo_scores` machinery and ID schema, rather than the legacy `*_scores_high` ID schema. It also supports automagically falling back to downloading legacy replays if a stable-imported score is requested for download (internally this happens via `legacy_score_id` in the `data` json). This change will allow replays to be downloaded, but it will still not yield 100% correct behaviour, as there is further work to be done in that respect. The download tracker is expecting score hashes to arrive from web to verify the integrity of the incoming download, but the API does not expose such a facility right now; we will have to decide as to whether we want to add one web-side, or whether we want to disable the checking client-side. --- osu.Game/Online/API/Requests/DownloadReplayRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/DownloadReplayRequest.cs b/osu.Game/Online/API/Requests/DownloadReplayRequest.cs index 77174f0bb5dd..3ea57cf6379e 100644 --- a/osu.Game/Online/API/Requests/DownloadReplayRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadReplayRequest.cs @@ -14,6 +14,6 @@ public DownloadReplayRequest(IScoreInfo score) protected override string FileExtension => ".osr"; - protected override string Target => $@"scores/{Model.Ruleset.ShortName}/{Model.OnlineID}/download"; + protected override string Target => $@"scores/{Model.OnlineID}/download"; } } From 118c86df342340da729c3931a4c58a1ed72e0f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 14:57:00 +0200 Subject: [PATCH 076/214] Fix `TestSceneUpdateBeatmapSetButton` using random difficulty count --- .../Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 585d93d3afb8..6d97be730b5a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -251,7 +251,7 @@ private BeatmapCarousel createCarousel() RelativeSizeAxes = Axes.Both, BeatmapSets = new List { - (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo()), + (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5)), } }; } From 07e126241da902fcf7471e28576c5204fc8b826d Mon Sep 17 00:00:00 2001 From: Wleter Date: Mon, 28 Aug 2023 16:41:55 +0200 Subject: [PATCH 077/214] working negative scaling --- .../SkinEditor/SkinSelectionHandler.cs | 41 ++++++------------- .../Edit/Compose/Components/SelectionBox.cs | 2 + .../SelectionBoxDragHandleContainer.cs | 12 ++++++ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index c90a1d8edf26..ae0a3d0635e2 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -31,8 +31,6 @@ public partial class SkinSelectionHandler : SelectionHandler mo public static void ApplyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable)); - protected override void OnOperationEnded() - { - base.OnOperationEnded(); - accumulatedNegativeScaling = 0; - } - protected override void OnSelectionChanged() { base.OnSelectionChanged(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 876e8ccbe997..a261b635b31e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -307,6 +307,8 @@ private SelectionBoxButton addButton(IconUsage icon, string tooltip, Action acti return button; } + public void ScaleHandlesFlip(Direction direction) => dragHandles.ScaleHandlesFlip(direction); + private void addScaleHandle(Anchor anchor) { var handle = new SelectionBoxScaleHandle diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 5c87271493f4..4fd2e9aba9b2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -69,6 +70,17 @@ private void bindDragHandle(SelectionBoxDragHandle handle) allDragHandles.Add(handle); } + public void ScaleHandlesFlip(Direction direction) + { + foreach (var handle in scaleHandles) + { + if (direction == Direction.Horizontal && !handle.Anchor.HasFlagFast(Anchor.x1)) + handle.Anchor ^= Anchor.x0 | Anchor.x2; + if (direction == Direction.Vertical && !handle.Anchor.HasFlagFast(Anchor.y1)) + handle.Anchor ^= Anchor.y0 | Anchor.y2; + } + } + private SelectionBoxRotationHandle displayedRotationHandle; private SelectionBoxDragHandle activeHandle; From 390b6f649dd9b9c6408d96b8a9347ba71b12c4a1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 29 Aug 2023 03:46:11 +0900 Subject: [PATCH 078/214] Add test --- .../Mods/TestSceneManiaModDoubleTime.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs new file mode 100644 index 000000000000..08e83b04b559 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public partial class TestSceneManiaModDoubleTime : ModTestScene + { + private const double offset = 18; + + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData + { + Mod = new ModNoMod(), + PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 && Player.ScoreProcessor.Accuracy.Value != 1, + Autoplay = false, + Beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + Difficulty = { OverallDifficulty = 10 }, + HitObjects = new List + { + new Note { StartTime = 1000 } + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1000 + offset, ManiaAction.Key1) + } + }); + + [Test] + public void TestHitWindowWithDoubleTime() => CreateModTest(new ModTestData + { + Mod = new ManiaModDoubleTime(), + PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 && Player.ScoreProcessor.Accuracy.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + Difficulty = { OverallDifficulty = 10 }, + HitObjects = new List + { + new Note { StartTime = 1000 } + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1000 + offset, ManiaAction.Key1) + } + }); + } +} From 50235cc2459c43802099b7b803396fbacf208af6 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 28 Aug 2023 23:16:33 +0300 Subject: [PATCH 079/214] somewhat working prototype --- osu.Game/Overlays/Mods/ModMapInfoContainer.cs | 58 ++++++++++++++++ osu.Game/Overlays/Mods/ModMapInfoDisplay.cs | 66 +++++++------------ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 17 +++-- osu.Game/Screens/Select/BeatmapDetailArea.cs | 4 ++ osu.Game/Screens/Select/BeatmapDetails.cs | 5 -- .../Screens/Select/Details/AdvancedStats.cs | 40 +++++------ osu.Game/Screens/Select/SongSelect.cs | 8 ++- 7 files changed, 121 insertions(+), 77 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModMapInfoContainer.cs diff --git a/osu.Game/Overlays/Mods/ModMapInfoContainer.cs b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs new file mode 100644 index 000000000000..eb5291b0a8d9 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +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.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.Select.Details; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Mods +{ + public partial class ModMapInfoContainer : Container + { + private ModMapInfoDisplay starRatingDisplay = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private Bindable adjustedInfo { get; set; } = null!; + private Bindable starRatingValue = new Bindable(); + + //public ModMapInfoContainer() + //{ + // + //} + + protected override void LoadComplete() + { + starRatingDisplay = new ModMapInfoDisplay("Star Rating", colours.ForStarDifficulty); + starRatingDisplay.Current.BindTo(starRatingValue); + + Content.Add(starRatingDisplay); + + adjustedInfo.BindValueChanged(e => { updateValues(); }, true); + } + + private void updateValues() + { + starRatingValue.Value = adjustedInfo.Value.StarRating; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs b/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs index 20666391d66b..ca815984ce4a 100644 --- a/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs +++ b/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; @@ -12,14 +13,13 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Select.Details; using osu.Game.Localisation; using osuTK; namespace osu.Game.Overlays.Mods { - public partial class ModMapInfoDisplay : Container, IHasCurrentValue + public partial class ModMapInfoDisplay : Container, IHasCurrentValue { public const float HEIGHT = 42; private const float transition_duration = 200; @@ -28,36 +28,40 @@ public partial class ModMapInfoDisplay : Container, IHasCurrentValue private readonly Box labelBackground; private readonly FillFlowContainer content; - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } - private readonly BindableWithCurrent current = new BindableWithCurrent(); + //public Bindable Current + //{ + // get => current.Current; + // set => current.Current = value; + //} + //private readonly BindableWithCurrent current = new BindableWithCurrent(); - [Resolved] - private OsuColour colours { get; set; } = null!; + public Bindable Current { get; set; } = new BindableWithCurrent(); + + //[Resolved] + //private OsuColour colours { get; set; } = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + protected Func GetColor; + /// /// Text to display in the left area of the display. /// - //protected abstract LocalisableString Label { get; } - protected LocalisableString Label => CommonStrings.Finish; - //protected string Label { get; } + protected LocalisableString Label; protected virtual float ValueAreaWidth => 56; - protected virtual string CounterFormat => @"N0"; + protected virtual string CounterFormat => @"0.00"; protected override Container Content => content; protected readonly RollingCounter Counter; - public ModMapInfoDisplay() + public ModMapInfoDisplay(LocalisableString label, Func colorFunc) { + Label = label; + GetColor = colorFunc; Height = HEIGHT; AutoSizeAxes = Axes.X; @@ -125,7 +129,7 @@ public ModMapInfoDisplay() { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Current = { BindTarget = Current.Value.StarRating } + Current = { BindTarget = Current } } } } @@ -146,39 +150,17 @@ protected override void LoadComplete() Current.BindValueChanged(e => { //var effect = CalculateEffectForComparison(e.NewValue.CompareTo(Current.Default)); - setColours(e.NewValue.StarRating.Value); + setColours(e.NewValue); }, true); } /// /// Fades colours of text and its background according to displayed value. /// - /// random number. - private void setColours(double stars) - { - contentBackground.FadeColour(colours.ForStarDifficulty(stars), transition_duration, Easing.OutQuint); - } - - /// - /// Converts signed integer into . Negative values are counted as difficulty reduction, positive as increase. - /// - /// Value to convert. Will arrive from comparison between bindable once it changes and it's . - /// Effect of the value. - protected virtual ModEffect CalculateEffectForComparison(int comparison) - { - if (comparison == 0) - return ModEffect.NotChanged; - if (comparison < 0) - return ModEffect.DifficultyReduction; - - return ModEffect.DifficultyIncrease; - } - - protected enum ModEffect + /// value + private void setColours(double value) { - NotChanged, - DifficultyReduction, - DifficultyIncrease + contentBackground.FadeColour(GetColor(value), transition_duration, Easing.OutQuint); } private partial class EffectCounter : RollingCounter diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ef5f6cf32351..a178f2e9dce5 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -25,9 +25,9 @@ using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select.Details; using osu.Game.Utils; using osuTK; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Overlays.Mods @@ -124,7 +124,8 @@ protected virtual IEnumerable CreateFooterButtons() private Container aboveColumnsContent = null!; private DifficultyMultiplierDisplay? multiplierDisplay; - private ModMapInfoDisplay mapInfoDisplay = null!; + + private ModMapInfoContainer mapInfoContainer = null!; protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } @@ -221,7 +222,7 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio) }); } - aboveColumnsContent.Add(mapInfoDisplay = new ModMapInfoDisplay + aboveColumnsContent.Add(mapInfoContainer = new ModMapInfoContainer { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft @@ -251,11 +252,6 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio) globalAvailableMods.BindTo(game.AvailableMods); } - - public void SetBindedMapStats(Bindable stats) - { - mapInfoDisplay.Current = stats; - } public override void Hide() { base.Hide(); @@ -282,6 +278,7 @@ protected override void LoadComplete() SelectedMods.BindValueChanged(_ => { + updateMapInfo(); updateMultiplier(); updateFromExternalSelection(); updateCustomisation(); @@ -413,8 +410,10 @@ private void updateMultiplier() private void updateMapInfo() { - if (mapInfoDisplay == null) + if (mapInfoContainer == null) return; + + //mapInfoDisplay.Current.Value = 5; } private void updateCustomisation() diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 595b86924b29..d43831e5763b 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,6 +31,9 @@ public virtual WorkingBeatmap Beatmap public readonly BeatmapDetails Details; + //[Cached] + //public Bindable AdjustedInfo { get; private set; } = new Bindable(); + protected Bindable CurrentTab => tabControl.Current; protected Bindable CurrentModsFilter => tabControl.CurrentModsFilter; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index c56411bef555..6ebdca1b8d44 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -274,11 +274,6 @@ private void updateMetrics() loading.Hide(); } - public Bindable GetBindedAdjustedMapStats() - { - return advanced.AdjustedMapStats.GetBoundCopy(); - } - private partial class DetailBox : Container { private readonly Container content; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 016e4ff2df0b..8f609888df15 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -46,6 +46,11 @@ public partial class AdvancedStats : Container private IBeatmapInfo beatmapInfo; +#nullable enable + [Resolved] + private Bindable? adjustedInfo { get; set; } = null; +#nullable disable + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; @@ -99,6 +104,21 @@ protected override void LoadComplete() private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedStatisticsUpdate; + private void updateBindedInfo() + { + if (adjustedInfo == null) return; + + BeatmapInfo adjusted = (BeatmapInfo)beatmapInfo; + adjusted.Difficulty.CircleSize = FirstValue.Value.adjustedValue ?? 0; + adjusted.Difficulty.DrainRate = HpDrain.Value.adjustedValue ?? 0; + adjusted.Difficulty.ApproachRate = ApproachRate.Value.adjustedValue ?? 5; + adjusted.Difficulty.OverallDifficulty = Accuracy.Value.adjustedValue ?? 0; + adjusted.StarRating = starDifficulty.Value.adjustedValue ?? 0; + + adjustedInfo.Value = adjusted; + adjustedInfo.TriggerChange(); + } + private void modsChanged(ValueChangedEvent> mods) { modSettingChangeTracker?.Dispose(); @@ -113,8 +133,6 @@ private void modsChanged(ValueChangedEvent> mods) updateStatistics(); } - public Bindable AdjustedMapStats = new Bindable(); - private void updateStatistics() { IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; @@ -149,13 +167,6 @@ private void updateStatistics() updateStarDifficulty(); - var temp = AdjustedMapStats.Value; - temp.CS.Value = FirstValue.Value.adjustedValue ?? 0; - temp.HP.Value = HpDrain.Value.adjustedValue ?? 0; - temp.OD.Value = Accuracy.Value.adjustedValue ?? 0; - temp.AR.Value = ApproachRate.Value.adjustedValue ?? 5; - AdjustedMapStats.Value = temp; - } private CancellationTokenSource starDifficultyCancellationSource; @@ -188,10 +199,7 @@ private void updateStarDifficulty() => Scheduler.AddOnce(() => return; starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); - - var temp = AdjustedMapStats.Value; - temp.StarRating.Value = moddedDifficulty.Value.Stars; - AdjustedMapStats.Value = temp; + updateBindedInfo(); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); }); @@ -311,10 +319,4 @@ public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) } } } - public struct MapStats - { - public Bindable StarRating; - public Bindable MinBPM, MaxBPM, AvgBPM; - public Bindable CS, HP, AR, OD; - } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 17b1b1f870f1..4567869e8e29 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -99,6 +99,9 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind [Resolved] private Bindable> selectedMods { get; set; } = null!; + [Cached] + private Bindable adjustedInfo { get; set; } = new Bindable(); + protected BeatmapCarousel Carousel { get; private set; } = null!; private ParallaxContainer wedgeBackground = null!; @@ -305,8 +308,8 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog // therein it will be registered at the `OsuGame` level to properly function as a blocking overlay. LoadComponent(ModSelect = CreateModSelectOverlay()); - var bindedStats = BeatmapDetails.Details.GetBindedAdjustedMapStats(); - ModSelect.SetBindedMapStats(bindedStats); + //var bindedStats = BeatmapDetails.Details.GetBindedAdjustedMapStats(); + //ModSelect.SetBindedMapStats(bindedStats); if (Footer != null) { @@ -583,6 +586,7 @@ public override void OnEntering(ScreenTransitionEvent e) FilterControl.Activate(); ModSelect.SelectedMods.BindTo(selectedMods); + //BeatmapDetails.AdjustedInfo.BindTo(adjustedInfo); beginLooping(); } From d07530b24102faaf6929ce12221bc5903d126095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 29 Aug 2023 11:48:47 +0200 Subject: [PATCH 080/214] Mark "Score V2" mod as not user-playable The mod generally will only be present on scores imported from stable. As such, it's probably ok to mark it as such. The primary reason for this change is to address #24436 (Score V2 being visible on beatmap overlay leaderboard mod selector). There is one possibly-unintended consequence of this change, namely that the results screen uses `UserPlayable` to determine as to whether animations should be played back, with the intention of turning off the animation playback for autoplay scores specifically. Therefore, turning off this flag will mean that the results screen animations will not play out for Score V2 scores - but I tend to consider this as either largely unimportant, or something that should be fixed in some other way (possibly by checking against the autoplay mod directly). Other usages of `UserPlayable` are either innocuous, or straight-up good safeties going forward in the context of Score V2 (guards against selection in mod select overlays, against score submission with the mod). --- osu.Game/Rulesets/Mods/ModScoreV2.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModScoreV2.cs b/osu.Game/Rulesets/Mods/ModScoreV2.cs index 6d56b2d86f69..df83d9676993 100644 --- a/osu.Game/Rulesets/Mods/ModScoreV2.cs +++ b/osu.Game/Rulesets/Mods/ModScoreV2.cs @@ -16,5 +16,6 @@ public class ModScoreV2 : Mod public override ModType Type => ModType.System; public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active."; public override double ScoreMultiplier => 1; + public override bool UserPlayable => false; } } From 3c575516ab2f642431e0e4db16f6303d5b3c7e42 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 17:06:23 +0200 Subject: [PATCH 081/214] add correct scaling for 90 degrees rotation --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index ae0a3d0635e2..ff6b52ca4dfe 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -48,10 +48,6 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) // copy to mutate, as we will need to compare to the original later on. var adjustedRect = selectionRect; - // first, remove any scale axis we are not interested in. - if (anchor.HasFlagFast(Anchor.x1)) scale.X = 0; - if (anchor.HasFlagFast(Anchor.y1)) scale.Y = 0; - // for now aspect lock scale adjustments that occur at corners.. if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) { @@ -61,7 +57,7 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) } // ..or if any of the selection have been rotated. // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0))) + else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0))) { if (anchor.HasFlagFast(Anchor.x1)) // if dragging from the horizontal centre, only a vertical component is available. @@ -115,6 +111,11 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) ); updateDrawablePosition(drawableItem, newPositionInAdjusted); + + if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) + { + scaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + } drawableItem.Scale *= scaledDelta; } From d56ab0fe9af86a96ebf2fe3047d0b059b29aedbb Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 17:25:52 +0200 Subject: [PATCH 082/214] change names --- .../SkinEditor/SkinSelectionHandler.cs | 25 ++++++++++--------- .../Edit/Compose/Components/SelectionBox.cs | 2 +- .../SelectionBoxDragHandleContainer.cs | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index ff6b52ca4dfe..afa592dfba31 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -73,18 +73,20 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) adjustedRect.Width += scale.X; adjustedRect.Height += scale.Y; - if (adjustedRect.Width < 0) - { - SelectionBox.ScaleHandlesFlip(Direction.Horizontal); - HandleFlip(Direction.Horizontal, false); - } - if (adjustedRect.Height < 0) - { - SelectionBox.ScaleHandlesFlip(Direction.Vertical); - HandleFlip(Direction.Vertical, false); - } if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + { + if (adjustedRect.Width < 0) + { + SelectionBox.FlipScaleHandles(Direction.Horizontal); + HandleFlip(Direction.Horizontal, false); + } + if (adjustedRect.Height < 0) + { + SelectionBox.FlipScaleHandles(Direction.Vertical); + HandleFlip(Direction.Vertical, false); + } return true; + } // scale adjust applied to each individual item should match that of the quad itself. var scaledDelta = new Vector2( @@ -113,9 +115,8 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) updateDrawablePosition(drawableItem, newPositionInAdjusted); if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) - { scaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - } + drawableItem.Scale *= scaledDelta; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index a261b635b31e..bbf9ea8c3c5b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -307,7 +307,7 @@ private SelectionBoxButton addButton(IconUsage icon, string tooltip, Action acti return button; } - public void ScaleHandlesFlip(Direction direction) => dragHandles.ScaleHandlesFlip(direction); + public void FlipScaleHandles(Direction direction) => dragHandles.FlipScaleHandles(direction); private void addScaleHandle(Anchor anchor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 4fd2e9aba9b2..e7f69b7b3700 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -70,7 +70,7 @@ private void bindDragHandle(SelectionBoxDragHandle handle) allDragHandles.Add(handle); } - public void ScaleHandlesFlip(Direction direction) + public void FlipScaleHandles(Direction direction) { foreach (var handle in scaleHandles) { From 586ce6e8d3e6d9ebdbb629d3dff76a6539739cd4 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 17:47:42 +0200 Subject: [PATCH 083/214] fix multiple selected --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index afa592dfba31..971ea3d266b5 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -73,14 +73,14 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) adjustedRect.Width += scale.X; adjustedRect.Height += scale.Y; - if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) { - if (adjustedRect.Width < 0) + if (adjustedRect.Width <= 0) { SelectionBox.FlipScaleHandles(Direction.Horizontal); HandleFlip(Direction.Horizontal, false); } - if (adjustedRect.Height < 0) + if (adjustedRect.Height <= 0) { SelectionBox.FlipScaleHandles(Direction.Vertical); HandleFlip(Direction.Vertical, false); @@ -114,10 +114,11 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) updateDrawablePosition(drawableItem, newPositionInAdjusted); + var currentScaledDelta = scaledDelta; if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) - scaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - drawableItem.Scale *= scaledDelta; + drawableItem.Scale *= currentScaledDelta; } return true; From ce1bc7156727ec30c059ab44dab689c4c6d990cc Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 29 Aug 2023 18:41:56 +0200 Subject: [PATCH 084/214] formatting --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 971ea3d266b5..2acddff0e411 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -80,11 +80,13 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) SelectionBox.FlipScaleHandles(Direction.Horizontal); HandleFlip(Direction.Horizontal, false); } + if (adjustedRect.Height <= 0) { SelectionBox.FlipScaleHandles(Direction.Vertical); HandleFlip(Direction.Vertical, false); } + return true; } From b658b0e3462170adcb3a57901de17ee514a8d9c1 Mon Sep 17 00:00:00 2001 From: Nabile Rahmani Date: Tue, 29 Aug 2023 23:28:50 +0200 Subject: [PATCH 085/214] Fix and use score user's IsBot property in results screen animation While a mod-created replay did flag itself as performed by a bot, the extension method converting it into a Score did not copy all the generated properties. As noted, it might be preferable for ModCreatedUser to inherit APIUser and forward it as-is to the Score instance. Related to PR #24675 --- osu.Game/Rulesets/Mods/ModExtensions.cs | 4 +++- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModExtensions.cs b/osu.Game/Rulesets/Mods/ModExtensions.cs index b22030414b27..49a3c9178a92 100644 --- a/osu.Game/Rulesets/Mods/ModExtensions.cs +++ b/osu.Game/Rulesets/Mods/ModExtensions.cs @@ -21,8 +21,10 @@ public static Score CreateScoreFromReplayData(this ICreateReplayData mod, IBeatm { User = new APIUser { - Id = APIUser.SYSTEM_USER_ID, + // TODO: Some fields weren't copied from replayData.User (namely IsBot and Id). Should ModCreatedUser inherit from APIUser so we could pass it verbatim to avoid future mistakes ? + Id = replayData.User.OnlineID, Username = replayData.User.Username, + IsBot = replayData.User.IsBot, } } }; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 1d0499c9093a..03231f9329d6 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -156,7 +156,7 @@ private void load(AudioManager audio) if (Score != null) { // only show flair / animation when arriving after watching a play that isn't autoplay. - bool shouldFlair = player != null && Score.Mods.All(m => m.UserPlayable); + bool shouldFlair = player != null && !Score.User.IsBot; ScorePanelList.AddScore(Score, shouldFlair); } From 270e2a66009b7b551837ad39ed45be9d3cdbf3b9 Mon Sep 17 00:00:00 2001 From: Nabile Rahmani Date: Tue, 29 Aug 2023 23:59:08 +0200 Subject: [PATCH 086/214] Update osu.Game/Rulesets/Mods/ModExtensions.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Mods/ModExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModExtensions.cs b/osu.Game/Rulesets/Mods/ModExtensions.cs index 49a3c9178a92..aa106f1203d4 100644 --- a/osu.Game/Rulesets/Mods/ModExtensions.cs +++ b/osu.Game/Rulesets/Mods/ModExtensions.cs @@ -21,7 +21,6 @@ public static Score CreateScoreFromReplayData(this ICreateReplayData mod, IBeatm { User = new APIUser { - // TODO: Some fields weren't copied from replayData.User (namely IsBot and Id). Should ModCreatedUser inherit from APIUser so we could pass it verbatim to avoid future mistakes ? Id = replayData.User.OnlineID, Username = replayData.User.Username, IsBot = replayData.User.IsBot, From a85f0d57911fd880bb7a406cd5bd82d3934f0b50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Aug 2023 13:29:08 +0900 Subject: [PATCH 087/214] Allow saving changes in tournament system using `Ctrl`+`S` --- osu.Game.Tournament/SaveChangesOverlay.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs index 7838d4ba488a..0b5194a51fd3 100644 --- a/osu.Game.Tournament/SaveChangesOverlay.cs +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -7,13 +7,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Tournament { - internal partial class SaveChangesOverlay : CompositeDrawable + internal partial class SaveChangesOverlay : CompositeDrawable, IKeyBindingHandler { [Resolved] private TournamentGame tournamentGame { get; set; } = null!; @@ -78,6 +81,21 @@ private async Task checkForChanges() scheduleNextCheck(); } + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == PlatformAction.Save && !e.Repeat) + { + saveChangesButton.TriggerClick(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + private void scheduleNextCheck() => Scheduler.AddDelayed(() => checkForChanges().FireAndForget(), 1000); private void saveChanges() From 24d6cbefe12b5ac570db353ebaeb2c7e3e192dad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Aug 2023 13:44:59 +0900 Subject: [PATCH 088/214] Remove tournament client minimum window size This seemed like a good idea but people were using it with smaller resolutions, do let's just not do it. Addresses https://github.com/ppy/osu/discussions/24670. --- osu.Game.Tournament/TournamentGame.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index ba3b17b513e6..9baa448ab81b 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -48,8 +48,6 @@ private void load(FrameworkConfigManager frameworkConfig, GameHost host) { frameworkConfig.BindWith(FrameworkSetting.WindowedSize, windowSize); - windowSize.MinValue = new Size(TournamentSceneManager.REQUIRED_WIDTH, TournamentSceneManager.STREAM_AREA_HEIGHT); - windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode); Add(loadingSpinner = new LoadingSpinner(true, true) From 0f123fd8e0be4127405236fec5cb4a114a5893fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Aug 2023 08:20:57 +0200 Subject: [PATCH 089/214] Remove unused using directive --- osu.Game.Tournament/TournamentGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 9baa448ab81b..25dc8ae1e52a 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Drawing; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From f277909470749c7ecadae849f4b25e30b1bf0260 Mon Sep 17 00:00:00 2001 From: Wleter Date: Wed, 30 Aug 2023 09:16:16 +0200 Subject: [PATCH 090/214] maintain rotated selection's centre position --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 2acddff0e411..ff53095e22b6 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -47,6 +47,7 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) // copy to mutate, as we will need to compare to the original later on. var adjustedRect = selectionRect; + bool isRotated = false; // for now aspect lock scale adjustments that occur at corners.. if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) @@ -59,6 +60,7 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0))) { + isRotated = true; if (anchor.HasFlagFast(Anchor.x1)) // if dragging from the horizontal centre, only a vertical component is available. scale.X = scale.Y / selectionRect.Height * selectionRect.Width; @@ -70,6 +72,10 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; + // Maintain the selection's centre position if dragging from the centre anchors and selection is rotated. + if (isRotated && anchor.HasFlagFast(Anchor.x1)) adjustedRect.X -= scale.X / 2; + if (isRotated && anchor.HasFlagFast(Anchor.y1)) adjustedRect.Y -= scale.Y / 2; + adjustedRect.Width += scale.X; adjustedRect.Height += scale.Y; From c91031604c20043e5e9315ef2eff5de6c708344d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Aug 2023 21:41:57 +0300 Subject: [PATCH 091/214] Add test cases for hitting hit circles with "early fade" behaviour --- .../TestSceneHitCircleLateFade.cs | 74 ++++++++++++++++--- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 3c32b4fa6554..149fd613117b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -3,8 +3,10 @@ using System; using System.Linq; +using FFmpeg.AutoGen; using NUnit.Framework; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mods; @@ -13,6 +15,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Visual; using osuTK; @@ -23,7 +26,7 @@ public partial class TestSceneHitCircleLateFade : OsuTestScene private float? alphaAtMiss; [Test] - public void TestHitCircleClassicMod() + public void TestHitCircleClassicModMiss() { AddStep("Create hit circle", () => { @@ -61,8 +64,21 @@ public void TestHitCircleClassicAndApproachCircleOnlyHiddenMods() AddAssert("Transparent when missed", () => alphaAtMiss == 0); } + /// + /// No early fade is expected to be applied if the hit circle has been hit. + /// [Test] - public void TestHitCircleNoMod() + public void TestHitCircleClassicModHit() + { + AddStep("Create hit circle", () => + { + SelectedMods.Value = new Mod[] { new OsuModClassic() }; + createCircle(true); + }); + } + + [Test] + public void TestHitCircleNoModMiss() { AddStep("Create hit circle", () => { @@ -74,6 +90,16 @@ public void TestHitCircleNoMod() AddAssert("Opaque when missed", () => alphaAtMiss == 1); } + [Test] + public void TestHitCircleNoModHit() + { + AddStep("Create hit circle", () => + { + SelectedMods.Value = Array.Empty(); + createCircle(true); + }); + } + [Test] public void TestSliderClassicMod() { @@ -100,24 +126,27 @@ public void TestSliderNoMod() AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1); } - private void createCircle() + private void createCircle(bool auto = false) { alphaAtMiss = null; - DrawableHitCircle drawableHitCircle = new DrawableHitCircle(new HitCircle + TestDrawableHitCircle drawableHitCircle = new TestDrawableHitCircle(new HitCircle { StartTime = Time.Current + 500, - Position = new Vector2(250) - }); + Position = new Vector2(250), + }, auto); + + drawableHitCircle.Scale = new Vector2(2f); foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObject(drawableHitCircle); drawableHitCircle.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableHitCircle.OnNewResult += (_, _) => + drawableHitCircle.OnNewResult += (_, result) => { - alphaAtMiss = drawableHitCircle.Alpha; + if (!result.IsHit) + alphaAtMiss = drawableHitCircle.Alpha; }; Child = drawableHitCircle; @@ -138,6 +167,8 @@ private void createSlider() }) }); + drawableSlider.Scale = new Vector2(2f); + drawableSlider.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); drawableSlider.OnLoadComplete += _ => @@ -145,12 +176,35 @@ private void createSlider() foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObject(drawableSlider.HeadCircle); - drawableSlider.HeadCircle.OnNewResult += (_, _) => + drawableSlider.HeadCircle.OnNewResult += (_, result) => { - alphaAtMiss = drawableSlider.HeadCircle.Alpha; + if (!result.IsHit) + alphaAtMiss = drawableSlider.HeadCircle.Alpha; }; }; Child = drawableSlider; } + + protected partial class TestDrawableHitCircle : DrawableHitCircle + { + private readonly bool auto; + + public TestDrawableHitCircle(HitCircle h, bool auto) + : base(h) + { + this.auto = auto; + } + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (auto && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false) + { + // force success + ApplyResult(r => r.Type = HitResult.Great); + } + else + base.CheckForResult(userTriggered, timeOffset); + } + } } } From 993cebe785a07d8c9054a4e7bf6c36567b65ef61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Aug 2023 21:42:47 +0300 Subject: [PATCH 092/214] Fix mod "Classic" interfering with `DrawableHitCircle` animation while in hit state --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 250d97c5373b..e740e6d201db 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -85,13 +85,16 @@ public void ApplyToDrawableHitObject(DrawableHitObject obj) private void applyEarlyFading(DrawableHitCircle circle) { - circle.ApplyCustomUpdateState += (o, _) => + circle.ApplyCustomUpdateState += (dho, state) => { - using (o.BeginAbsoluteSequence(o.StateUpdateTime)) + using (dho.BeginAbsoluteSequence(dho.StateUpdateTime)) { - double okWindow = o.HitObject.HitWindows.WindowFor(HitResult.Ok); - double lateMissFadeTime = o.HitObject.HitWindows.WindowFor(HitResult.Meh) - okWindow; - o.Delay(okWindow).FadeOut(lateMissFadeTime); + if (state != ArmedState.Hit) + { + double okWindow = dho.HitObject.HitWindows.WindowFor(HitResult.Ok); + double lateMissFadeTime = dho.HitObject.HitWindows.WindowFor(HitResult.Meh) - okWindow; + dho.Delay(okWindow).FadeOut(lateMissFadeTime); + } } }; } From b8df714f56c162376396efa2ee71a8c14894e276 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Aug 2023 21:50:52 +0300 Subject: [PATCH 093/214] Remove unused using directives --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 149fd613117b..1557afbf8473 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -3,10 +3,8 @@ using System; using System.Linq; -using FFmpeg.AutoGen; using NUnit.Framework; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mods; From 2d88135198c245a1185fe904b0d61abbd483319f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 16:18:18 +0900 Subject: [PATCH 094/214] Add automated test coverage for new test --- .../TestSceneHitCircleLateFade.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 1557afbf8473..3b051912fd8a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -68,11 +68,17 @@ public void TestHitCircleClassicAndApproachCircleOnlyHiddenMods() [Test] public void TestHitCircleClassicModHit() { + TestDrawableHitCircle circle = null!; + AddStep("Create hit circle", () => { SelectedMods.Value = new Mod[] { new OsuModClassic() }; - createCircle(true); + circle = createCircle(true); }); + + AddUntilStep("Wait until circle is hit", () => circle.Result?.Type == HitResult.Great); + AddUntilStep("Wait for miss window", () => Clock.CurrentTime, () => Is.GreaterThanOrEqualTo(circle.HitObject.StartTime + circle.HitObject.HitWindows.WindowFor(HitResult.Miss))); + AddAssert("Check circle is still visible", () => circle.Alpha, () => Is.GreaterThan(0)); } [Test] @@ -124,7 +130,7 @@ public void TestSliderNoMod() AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1); } - private void createCircle(bool auto = false) + private TestDrawableHitCircle createCircle(bool shouldHit = false) { alphaAtMiss = null; @@ -132,7 +138,7 @@ private void createCircle(bool auto = false) { StartTime = Time.Current + 500, Position = new Vector2(250), - }, auto); + }, shouldHit); drawableHitCircle.Scale = new Vector2(2f); @@ -148,6 +154,8 @@ private void createCircle(bool auto = false) }; Child = drawableHitCircle; + + return drawableHitCircle; } private void createSlider() @@ -180,22 +188,23 @@ private void createSlider() alphaAtMiss = drawableSlider.HeadCircle.Alpha; }; }; + Child = drawableSlider; } protected partial class TestDrawableHitCircle : DrawableHitCircle { - private readonly bool auto; + private readonly bool shouldHit; - public TestDrawableHitCircle(HitCircle h, bool auto) + public TestDrawableHitCircle(HitCircle h, bool shouldHit) : base(h) { - this.auto = auto; + this.shouldHit = shouldHit; } protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (auto && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false) + if (shouldHit && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false) { // force success ApplyResult(r => r.Type = HitResult.Great); From 5fa31b7b359ca15a5a0cde035df2279a22f695d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:05:34 +0900 Subject: [PATCH 095/214] Fix schedule screen not responding to new matches being added --- .../Screens/Schedule/ScheduleScreen.cs | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 063c231add02..ee4b762be353 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,6 +20,7 @@ namespace osu.Game.Tournament.Screens.Schedule { public partial class ScheduleScreen : TournamentScreen { + private readonly BindableList allMatches = new BindableList(); private readonly Bindable currentMatch = new Bindable(); private Container mainContainer = null!; private LadderInfo ladder = null!; @@ -101,16 +103,23 @@ protected override void LoadComplete() { base.LoadComplete(); + allMatches.BindTo(ladder.Matches); + allMatches.BindCollectionChanged((_, _) => refresh()); + currentMatch.BindTo(ladder.CurrentMatch); - currentMatch.BindValueChanged(matchChanged, true); + currentMatch.BindValueChanged(_ => refresh(), true); } - private void matchChanged(ValueChangedEvent match) + private void refresh() { - var upcoming = ladder.Matches.Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); - var conditionals = ladder - .Matches.Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) - .SelectMany(m => m.ConditionalMatches.Where(cp => m.Acronyms.TrueForAll(a => cp.Acronyms.Contains(a)))); + IEnumerable upcoming = + allMatches + .Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); + + IEnumerable conditionals = + allMatches + .Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .SelectMany(m => m.ConditionalMatches.Where(cp => m.Acronyms.TrueForAll(a => cp.Acronyms.Contains(a)))); upcoming = upcoming.Concat(conditionals); upcoming = upcoming.OrderBy(p => p.Date.Value).Take(8); @@ -137,12 +146,12 @@ private void matchChanged(ValueChangedEvent match) { RelativeSizeAxes = Axes.Both, Width = 0.4f, - ChildrenEnumerable = ladder.Matches - .Where(p => p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null - && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) - .OrderByDescending(p => p.Date.Value) - .Take(8) - .Select(p => new ScheduleMatch(p)) + ChildrenEnumerable = allMatches + .Where(p => p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null + && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .OrderByDescending(p => p.Date.Value) + .Take(8) + .Select(p => new ScheduleMatch(p)) }, new ScheduleContainer("upcoming matches") { @@ -161,7 +170,7 @@ private void matchChanged(ValueChangedEvent match) } }; - if (match.NewValue != null) + if (currentMatch.Value != null) { comingUpNext.Child = new FillFlowContainer { @@ -170,12 +179,12 @@ private void matchChanged(ValueChangedEvent match) Spacing = new Vector2(30), Children = new Drawable[] { - new ScheduleMatch(match.NewValue, false) + new ScheduleMatch(currentMatch.Value, false) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - new TournamentSpriteTextWithBackground(match.NewValue.Round.Value?.Name.Value ?? string.Empty) + new TournamentSpriteTextWithBackground(currentMatch.Value.Round.Value?.Name.Value ?? string.Empty) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -185,7 +194,7 @@ private void matchChanged(ValueChangedEvent match) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = match.NewValue.Team1.Value?.FullName + " vs " + match.NewValue.Team2.Value?.FullName, + Text = currentMatch.Value.Team1.Value?.FullName + " vs " + currentMatch.Value.Team2.Value?.FullName, Font = OsuFont.Torus.With(size: 24, weight: FontWeight.SemiBold) }, new FillFlowContainer @@ -196,7 +205,7 @@ private void matchChanged(ValueChangedEvent match) Origin = Anchor.CentreLeft, Children = new Drawable[] { - new ScheduleMatchDate(match.NewValue.Date.Value) + new ScheduleMatchDate(currentMatch.Value.Date.Value) { Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Regular) } From 7aa1505062cd3d15082d3e10dbf9cc232048b6c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:13:36 +0900 Subject: [PATCH 096/214] Improve legibility of LINQ queries for recent/upcoming matches --- .../Screens/Schedule/ScheduleScreen.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index ee4b762be353..62c54daa75ad 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -112,17 +112,25 @@ protected override void LoadComplete() private void refresh() { - IEnumerable upcoming = - allMatches - .Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); + const int days_for_displays = 4; IEnumerable conditionals = allMatches - .Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .Where(m => !m.Completed.Value && (m.Team1.Value == null || m.Team2.Value == null) && Math.Abs(m.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < days_for_displays) .SelectMany(m => m.ConditionalMatches.Where(cp => m.Acronyms.TrueForAll(a => cp.Acronyms.Contains(a)))); - upcoming = upcoming.Concat(conditionals); - upcoming = upcoming.OrderBy(p => p.Date.Value).Take(8); + IEnumerable upcoming = + allMatches + .Where(m => !m.Completed.Value && m.Team1.Value != null && m.Team2.Value != null && Math.Abs(m.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < days_for_displays) + .Concat(conditionals) + .OrderBy(m => m.Date.Value) + .Take(8); + + var recent = + allMatches + .Where(m => m.Completed.Value && m.Team1.Value != null && m.Team2.Value != null && Math.Abs(m.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < days_for_displays) + .OrderByDescending(m => m.Date.Value) + .Take(8); ScheduleContainer comingUpNext; @@ -146,12 +154,7 @@ private void refresh() { RelativeSizeAxes = Axes.Both, Width = 0.4f, - ChildrenEnumerable = allMatches - .Where(p => p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null - && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) - .OrderByDescending(p => p.Date.Value) - .Take(8) - .Select(p => new ScheduleMatch(p)) + ChildrenEnumerable = recent.Select(p => new ScheduleMatch(p)) }, new ScheduleContainer("upcoming matches") { From 4b68493084a4f9b87bbead6fdb02367d5d062832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:05:18 +0900 Subject: [PATCH 097/214] Add ability to test recent / upcoming matches on schedule screen --- .../Screens/TestSceneScheduleScreen.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs index f3c3fdec9798..a58f09d13a84 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs @@ -12,6 +12,13 @@ namespace osu.Game.Tournament.Tests.Screens { public partial class TestSceneScheduleScreen : TournamentScreenTestScene { + public override void SetUpSteps() + { + AddStep("clear matches", () => Ladder.Matches.Clear()); + + base.SetUpSteps(); + } + [BackgroundDependencyLoader] private void load() { @@ -34,6 +41,36 @@ public void TestNoCurrentMatch() AddStep("Set null current match", () => Ladder.CurrentMatch.Value = null); } + [Test] + public void TestUpcomingMatches() + { + AddStep("Add upcoming match", () => + { + var tournamentMatch = CreateSampleMatch(); + + tournamentMatch.Date.Value = DateTimeOffset.UtcNow.AddMinutes(5); + tournamentMatch.Completed.Value = false; + + Ladder.Matches.Add(tournamentMatch); + }); + } + + [Test] + public void TestRecentMatches() + { + AddStep("Add recent match", () => + { + var tournamentMatch = CreateSampleMatch(); + + tournamentMatch.Date.Value = DateTimeOffset.UtcNow; + tournamentMatch.Completed.Value = true; + tournamentMatch.Team1Score.Value = tournamentMatch.PointsToWin; + tournamentMatch.Team2Score.Value = tournamentMatch.PointsToWin / 2; + + Ladder.Matches.Add(tournamentMatch); + }); + } + private void setMatchDate(TimeSpan relativeTime) // Humanizer cannot handle negative timespans. => AddStep($"start time is {relativeTime}", () => From 540c58c359ed47ee758519da0b089621be41b7cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:46:47 +0900 Subject: [PATCH 098/214] Fix tournament screen tests not matching `OsuGameBase` resolution This would cause things to look completely out of whack. The numbers here match `DrawSizePreservingFillContainer` defaults as used by `OsuGameBase.CreateScalingContainer()`. --- osu.Game.Tournament.Tests/TournamentScreenTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs b/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs index 8adffe14681b..e8cca00c923b 100644 --- a/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs @@ -23,7 +23,7 @@ private partial class TournamentScalingContainer : DrawSizePreservingFillContain { public TournamentScalingContainer() { - TargetDrawSize = new Vector2(1920, 1080); + TargetDrawSize = new Vector2(1024, 768); RelativeSizeAxes = Axes.Both; } From 4bdaca3816ad8cf6ddd3125fce83c5c11e975a20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:05:04 +0900 Subject: [PATCH 099/214] Fix regression in spacing on schedule screen --- osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 62c54daa75ad..d02559d6b7e2 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -294,6 +294,7 @@ public ScheduleContainer(string title) { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.Both, + Spacing = new Vector2(0, -6), Margin = new MarginPadding(10) }, } From 34b279845bf4d4569b8cc9897cba0b58e4219c20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 19:03:56 +0900 Subject: [PATCH 100/214] Add more testability for song bar --- osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index e0444b6126ed..95d6b6d107fd 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs @@ -58,9 +58,14 @@ public void TestSongBar() songBar.Beatmap = new TournamentBeatmap(beatmap); }); + AddStep("set mods to HR", () => songBar.Mods = LegacyMods.HardRock); AddStep("set mods to DT", () => songBar.Mods = LegacyMods.DoubleTime); AddStep("unset mods", () => songBar.Mods = LegacyMods.None); + + AddToggleStep("toggle expanded", expanded => songBar.Expanded = expanded); + + AddStep("set null beatmap", () => songBar.Beatmap = null); } } } From cf9c8120c5292774b8aec7d2b0282003cdca32d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 19:04:06 +0900 Subject: [PATCH 101/214] Fix potential race condition in song bar beatmap lookup flow Cancelling a web request may not necessarily cancel the callbacks. This might help with https://github.com/ppy/osu/issues/24598. --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index bf835122cc07..5407c2107942 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -92,8 +92,16 @@ private void load() else { beatmapLookupRequest = new GetBeatmapRequest(new APIBeatmap { OnlineID = beatmapId }); - beatmapLookupRequest.Success += b => Beatmap.Value = new TournamentBeatmap(b); - beatmapLookupRequest.Failure += _ => Beatmap.Value = null; + beatmapLookupRequest.Success += b => + { + if (lastBeatmapId == beatmapId) + Beatmap.Value = new TournamentBeatmap(b); + }; + beatmapLookupRequest.Failure += _ => + { + if (lastBeatmapId == beatmapId) + Beatmap.Value = null; + }; API.Queue(beatmapLookupRequest); } } From 47160f7744823a4f53716fbc001b707003430a15 Mon Sep 17 00:00:00 2001 From: Wleter Date: Thu, 31 Aug 2023 20:24:26 +0200 Subject: [PATCH 102/214] make getClosestAnchor work with rotated drawable --- .../SkinEditor/SkinSelectionHandler.cs | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 72216f040e2a..fe2816db6fe9 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -291,7 +291,7 @@ private static Anchor getClosestAnchor(Drawable drawable) if (parent == null) return drawable.Anchor; - var screenPosition = getScreenPosition(); + var screenPosition = drawable.ToScreenSpace(drawable.OriginPosition); var absolutePosition = parent.ToLocalSpace(screenPosition); var factor = parent.RelativeToAbsoluteFactor; @@ -313,26 +313,6 @@ static Anchor getAnchorFromPosition(float xOrY, Anchor anchor0, Anchor anchor1, result |= getAnchorFromPosition(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2); return result; - - Vector2 getScreenPosition() - { - var quad = drawable.ScreenSpaceDrawQuad; - var origin = drawable.Origin; - - var pos = quad.TopLeft; - - if (origin.HasFlagFast(Anchor.x2)) - pos.X += quad.Width; - else if (origin.HasFlagFast(Anchor.x1)) - pos.X += quad.Width / 2f; - - if (origin.HasFlagFast(Anchor.y2)) - pos.Y += quad.Height; - else if (origin.HasFlagFast(Anchor.y1)) - pos.Y += quad.Height / 2f; - - return pos; - } } private static void applyAnchor(Drawable drawable, Anchor anchor) From c75b14c72994c51a54b4f01fc16a4f52c22d9c4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Sep 2023 16:54:35 +0900 Subject: [PATCH 103/214] Apply NRT to new `LegacyHitPolicy` class (and other implementations) --- osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs | 4 +--- osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs | 7 ++++--- .../UI/StartTimeOrderedHitPolicy.cs | 14 +++++++++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs index 69bd360b5ef7..2c6895d7ec16 100644 --- a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -14,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.UI /// public class AnyOrderHitPolicy : IHitPolicy { - public IHitObjectContainer HitObjectContainer { get; set; } + public IHitObjectContainer HitObjectContainer { get; set; } = null!; public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result) => ClickAction.Hit; diff --git a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs index 2b6359fb4661..daf498581ef3 100644 --- a/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Rulesets.Objects; @@ -22,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI /// public class LegacyHitPolicy : IHitPolicy { - public IHitObjectContainer HitObjectContainer { get; set; } + public IHitObjectContainer? HitObjectContainer { get; set; } private readonly double hittableRange; @@ -37,6 +35,9 @@ public void HandleHit(DrawableHitObject hitObject) public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result) { + if (HitObjectContainer == null) + throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(CheckHittable)} is called."); + var aliveObjects = HitObjectContainer.AliveObjects.ToList(); int index = aliveObjects.IndexOf(hitObject); diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index 6fd0b512be19..2b24fb93986c 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Game.Rulesets.Objects; @@ -23,11 +21,14 @@ namespace osu.Game.Rulesets.Osu.UI /// public class StartTimeOrderedHitPolicy : IHitPolicy { - public IHitObjectContainer HitObjectContainer { get; set; } + public IHitObjectContainer? HitObjectContainer { get; set; } public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult _) { - DrawableHitObject blockingObject = null; + if (HitObjectContainer == null) + throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(CheckHittable)} is called."); + + DrawableHitObject? blockingObject = null; foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) { @@ -48,6 +49,9 @@ public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitRe public void HandleHit(DrawableHitObject hitObject) { + if (HitObjectContainer == null) + throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(HandleHit)} is called."); + // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners). if (!hitObjectCanBlockFutureHits(hitObject)) return; @@ -75,7 +79,7 @@ private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject) private IEnumerable enumerateHitObjectsUpTo(double targetTime) { - foreach (var obj in HitObjectContainer.AliveObjects) + foreach (var obj in HitObjectContainer!.AliveObjects) { if (obj.HitObject.StartTime >= targetTime) yield break; From ede9fae38166e69e85ee7fbd643109b218eea4d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Sep 2023 17:19:39 +0900 Subject: [PATCH 104/214] Remove broken check from `TestSceneHitCircleLateFade` --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 3b051912fd8a..d74a31ada481 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -204,7 +204,7 @@ public TestDrawableHitCircle(HitCircle h, bool shouldHit) protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (shouldHit && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false) + if (shouldHit && !userTriggered && timeOffset >= 0) { // force success ApplyResult(r => r.Type = HitResult.Great); From fc4069f794e91e6ea1eb4a4b28c334b12e301a85 Mon Sep 17 00:00:00 2001 From: Wleter Date: Fri, 1 Sep 2023 13:01:51 +0200 Subject: [PATCH 105/214] let SelectionBox perform flip with scale handles --- .../SkinEditor/SkinSelectionHandler.cs | 14 ++++--------- .../Edit/Compose/Components/SelectionBox.cs | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index ff53095e22b6..b30351f61b1c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -81,18 +81,12 @@ public override bool HandleScale(Vector2 scale, Anchor anchor) if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) { - if (adjustedRect.Width <= 0) - { - SelectionBox.FlipScaleHandles(Direction.Horizontal); - HandleFlip(Direction.Horizontal, false); - } + Axes toFlip = Axes.None; - if (adjustedRect.Height <= 0) - { - SelectionBox.FlipScaleHandles(Direction.Vertical); - HandleFlip(Direction.Vertical, false); - } + if (adjustedRect.Width <= 0) toFlip |= Axes.X; + if (adjustedRect.Height <= 0) toFlip |= Axes.Y; + SelectionBox.PerformFlipFromScaleHandles(toFlip); return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index bbf9ea8c3c5b..0c19f6c62e59 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -307,7 +308,24 @@ private SelectionBoxButton addButton(IconUsage icon, string tooltip, Action acti return button; } - public void FlipScaleHandles(Direction direction) => dragHandles.FlipScaleHandles(direction); + /// + /// This method should be called when a selection needs to be flipped + /// because of an ongoing scale handle drag that would otherwise cause width or height to go negative. + /// + public void PerformFlipFromScaleHandles(Axes axes) + { + if (axes.HasFlagFast(Axes.X)) + { + dragHandles.FlipScaleHandles(Direction.Horizontal); + OnFlip?.Invoke(Direction.Horizontal, false); + } + + if (axes.HasFlagFast(Axes.Y)) + { + dragHandles.FlipScaleHandles(Direction.Vertical); + OnFlip?.Invoke(Direction.Vertical, false); + } + } private void addScaleHandle(Anchor anchor) { From f74dfa9c6cc23c2807225b3e69be4f6ba446adac Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Sep 2023 22:55:32 +0300 Subject: [PATCH 106/214] Apply flashlight slider dim immediately to match osu!(stable) --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index efeac9a18016..765af8095925 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -69,7 +69,7 @@ public OsuFlashlight(OsuModFlashlight modFlashlight) public void OnSliderTrackingChange(ValueChangedEvent e) { // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. - this.TransformTo(nameof(FlashlightDim), e.NewValue ? 0.8f : 0.0f, 50); + FlashlightDim = e.NewValue ? 0.8f : 0.0f; } protected override bool OnMouseMove(MouseMoveEvent e) From 920c6fa48facbd43c5edbae6ea1e66468fadc9b3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Sep 2023 23:29:51 +0300 Subject: [PATCH 107/214] Update comment --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 765af8095925..252d7e276252 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -68,7 +68,7 @@ public OsuFlashlight(OsuModFlashlight modFlashlight) public void OnSliderTrackingChange(ValueChangedEvent e) { - // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. + // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield. FlashlightDim = e.NewValue ? 0.8f : 0.0f; } From b0398b62595bbf099ce957cafe9af3ea9769f16a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 3 Sep 2023 02:09:01 +0300 Subject: [PATCH 108/214] functionality is done --- osu.Game/Beatmaps/BeatmapShortInfo.cs | 29 +++ osu.Game/Overlays/Mods/ModMapInfoContainer.cs | 171 +++++++++++++--- osu.Game/Overlays/Mods/ModMapInfoDisplay.cs | 185 ------------------ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 17 +- .../Overlays/Mods/VerticalAttributeDisplay.cs | 76 +++++++ osu.Game/Screens/Select/BeatmapDetailArea.cs | 4 - .../Screens/Select/Details/AdvancedStats.cs | 33 ++-- osu.Game/Screens/Select/SongSelect.cs | 2 +- 8 files changed, 287 insertions(+), 230 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapShortInfo.cs delete mode 100644 osu.Game/Overlays/Mods/ModMapInfoDisplay.cs create mode 100644 osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs diff --git a/osu.Game/Beatmaps/BeatmapShortInfo.cs b/osu.Game/Beatmaps/BeatmapShortInfo.cs new file mode 100644 index 000000000000..633e12d1b240 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapShortInfo.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Beatmaps +{ + public class BeatmapShortInfo : IEquatable + { + public StarDifficulty StarDifficulty; + public float CircleSize; + public float DrainRate; + public float ApproachRate; + public float OverallDifficulty; + public double BPM; + public bool Equals(BeatmapShortInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return StarDifficulty.Stars == other.StarDifficulty.Stars && + CircleSize.Equals(other.CircleSize) && + DrainRate.Equals(other.DrainRate) && + ApproachRate.Equals(other.ApproachRate) && + OverallDifficulty.Equals(other.OverallDifficulty) && + BPM.Equals(other.BPM); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModMapInfoContainer.cs b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs index eb5291b0a8d9..378e6f605705 100644 --- a/osu.Game/Overlays/Mods/ModMapInfoContainer.cs +++ b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs @@ -1,24 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +#nullable disable + using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.BeatmapSet; -using osu.Game.Resources.Localisation.Web; -using osu.Game.Screens.Select.Details; using osuTK; using osuTK.Graphics; @@ -26,33 +23,161 @@ namespace osu.Game.Overlays.Mods { public partial class ModMapInfoContainer : Container { - private ModMapInfoDisplay starRatingDisplay = null!; + private Container content; + private Container innerContent; + + private Box background; + private Box innerBackground; + + private StarRatingDisplay starRatingDisplay; + private BPMDisplay bpmDisplay; + + private VerticalAttributeDisplay circleSizeDisplay; + private VerticalAttributeDisplay drainRateDisplay; + private VerticalAttributeDisplay approachRateDisplay; + private VerticalAttributeDisplay overallDifficultyDisplay; [Resolved] - private OsuColour colours { get; set; } = null!; + private OverlayColourProvider colourProvider { get; set; } [Resolved] - private Bindable adjustedInfo { get; set; } = null!; - private Bindable starRatingValue = new Bindable(); + private Bindable adjustedInfo { get; set; } - //public ModMapInfoContainer() - //{ - // - //} + public ModMapInfoContainer() + { + // values as ModSelectOverlay footer buttons + const float shear = ShearedOverlayContainer.SHEAR; + const float corner_radius = 7; + const float border_thickness = 2; + InternalChild = content = new InputBlockingContainer + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomRight, + AutoSizeAxes = Axes.X, + Height = 50, // as ModSelectOverlay footer buttons + Shear = new Vector2(shear, 0), + CornerRadius = corner_radius, + BorderThickness = border_thickness, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer // divide inner and outer content + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + innerContent = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + BorderThickness = border_thickness, + CornerRadius = corner_radius, + Masking = true, + Children = new Drawable[] + { + innerBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer // actual inner content + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Horizontal = 15 }, + Children = new Drawable[] + { + new Container // wrap to reserve space for StarRatingDisplay + { + Width = 70, // can be up to 70px on extra high SR + Child = starRatingDisplay = new StarRatingDisplay(default, animated: true) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Shear = new Vector2(-shear, 0), + } + }, + new Container // wrap to reserve space for BPM + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 70, + Child = bpmDisplay = new BPMDisplay + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Shear = new Vector2(-shear, 0), + } + } + } + } + } + }, + new FillFlowContainer // outer content + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new[] + { + circleSizeDisplay = new VerticalAttributeDisplay("CS"), + drainRateDisplay = new VerticalAttributeDisplay("HP"), + approachRateDisplay = new VerticalAttributeDisplay("AR"), + overallDifficultyDisplay = new VerticalAttributeDisplay("OD"), + } + } + } + } + } + }; + } protected override void LoadComplete() { - starRatingDisplay = new ModMapInfoDisplay("Star Rating", colours.ForStarDifficulty); - starRatingDisplay.Current.BindTo(starRatingValue); + adjustedInfo.BindValueChanged(e => { UpdateValues(); }, true); - Content.Add(starRatingDisplay); + background.Colour = colourProvider.Background4; + innerBackground.Colour = colourProvider.Background3; + Color4 glow_colour = colourProvider.Background1; - adjustedInfo.BindValueChanged(e => { updateValues(); }, true); + content.BorderColour = ColourInfo.GradientVertical(background.Colour, glow_colour); + innerContent.BorderColour = ColourInfo.GradientVertical(innerBackground.Colour, glow_colour); } - private void updateValues() + public void UpdateValues() { - starRatingValue.Value = adjustedInfo.Value.StarRating; + if (adjustedInfo.Value == null) return; + + starRatingDisplay.Current.Value = adjustedInfo.Value.StarDifficulty; + bpmDisplay.Current.Value = adjustedInfo.Value.BPM; + + circleSizeDisplay.Current.Value = adjustedInfo.Value.CircleSize; + drainRateDisplay.Current.Value = adjustedInfo.Value.DrainRate; + approachRateDisplay.Current.Value = adjustedInfo.Value.ApproachRate; + overallDifficultyDisplay.Current.Value = adjustedInfo.Value.OverallDifficulty; + } + + private partial class BPMDisplay : RollingCounter + { + protected override double RollingDuration => 500; + + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0 BPM"); + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 20, weight: FontWeight.SemiBold) + }; } } } diff --git a/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs b/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs deleted file mode 100644 index ca815984ce4a..000000000000 --- a/osu.Game/Overlays/Mods/ModMapInfoDisplay.cs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.LocalisationExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Localisation; -using osuTK; - - -namespace osu.Game.Overlays.Mods -{ - public partial class ModMapInfoDisplay : Container, IHasCurrentValue - { - public const float HEIGHT = 42; - private const float transition_duration = 200; - - private readonly Box contentBackground; - private readonly Box labelBackground; - private readonly FillFlowContainer content; - - //public Bindable Current - //{ - // get => current.Current; - // set => current.Current = value; - //} - //private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current { get; set; } = new BindableWithCurrent(); - - //[Resolved] - //private OsuColour colours { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - protected Func GetColor; - - /// - /// Text to display in the left area of the display. - /// - protected LocalisableString Label; - - protected virtual float ValueAreaWidth => 56; - - protected virtual string CounterFormat => @"0.00"; - - protected override Container Content => content; - - protected readonly RollingCounter Counter; - - public ModMapInfoDisplay(LocalisableString label, Func colorFunc) - { - Label = label; - GetColor = colorFunc; - Height = HEIGHT; - AutoSizeAxes = Axes.X; - - InternalChild = new InputBlockingContainer - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), - Children = new Drawable[] - { - contentBackground = new Box - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Y, - Width = ValueAreaWidth + ModSelectPanel.CORNER_RADIUS - }, - new GridContainer - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, ValueAreaWidth) - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - Children = new Drawable[] - { - labelBackground = new Box - { - RelativeSizeAxes = Axes.Both - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = 18 }, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Text = Label, - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) - } - } - }, - content = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Spacing = new Vector2(2, 0), - Child = Counter = new EffectCounter(CounterFormat) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = { BindTarget = Current } - } - } - } - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load() - { - labelBackground.Colour = colourProvider.Background4; - } - - protected override void LoadComplete() - { - Current.BindValueChanged(e => - { - //var effect = CalculateEffectForComparison(e.NewValue.CompareTo(Current.Default)); - setColours(e.NewValue); - }, true); - } - - /// - /// Fades colours of text and its background according to displayed value. - /// - /// value - private void setColours(double value) - { - contentBackground.FadeColour(GetColor(value), transition_duration, Easing.OutQuint); - } - - private partial class EffectCounter : RollingCounter - { - private readonly string? format; - - public EffectCounter(string? format) - { - this.format = format; - } - - protected override double RollingDuration => 500; - - protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(format); - - protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText - { - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) - }; - } - } -} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a178f2e9dce5..426c42454176 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -222,13 +222,18 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio) }); } - aboveColumnsContent.Add(mapInfoContainer = new ModMapInfoContainer + FooterContent.Add(mapInfoContainer = new ModMapInfoContainer { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Padding = new MarginPadding + { + Vertical = PADDING, + Horizontal = 70 + }, }); - FooterContent.Child = footerButtonFlow = new FillFlowContainer + FooterContent.Add(footerButtonFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -248,7 +253,7 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio) DarkerColour = colours.Pink2, LighterColour = colours.Pink1 }) - }; + }); globalAvailableMods.BindTo(game.AvailableMods); } @@ -413,7 +418,7 @@ private void updateMapInfo() if (mapInfoContainer == null) return; - //mapInfoDisplay.Current.Value = 5; + mapInfoContainer.UpdateValues(); } private void updateCustomisation() diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs new file mode 100644 index 000000000000..2ad420657c9a --- /dev/null +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -0,0 +1,76 @@ +// 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.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + + +namespace osu.Game.Overlays.Mods +{ + public partial class VerticalAttributeDisplay : Container, IHasCurrentValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + /// + /// Text to display in the top area of the display. + /// + public LocalisableString Label { get; protected set; } + + public VerticalAttributeDisplay(LocalisableString label) + { + Label = label; + AutoSizeAxes = Axes.X; + Origin = Anchor = Anchor.CentreLeft; + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0); + InternalChild = new FillFlowContainer + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Text = Label, + Margin = new MarginPadding { Horizontal = 15 }, // to reserve space for 0.XX value + Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold) + }, + new EffectCounter() + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Current = { BindTarget = Current } + } + } + }; + } + + private partial class EffectCounter : RollingCounter + { + protected override double RollingDuration => 500; + + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0.##"); + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 18, weight: FontWeight.SemiBold) + }; + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index d43831e5763b..595b86924b29 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -31,9 +30,6 @@ public virtual WorkingBeatmap Beatmap public readonly BeatmapDetails Details; - //[Cached] - //public Bindable AdjustedInfo { get; private set; } = new Bindable(); - protected Bindable CurrentTab => tabControl.Current; protected Bindable CurrentModsFilter => tabControl.CurrentModsFilter; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 8f609888df15..31fac8b4a05e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -46,10 +46,8 @@ public partial class AdvancedStats : Container private IBeatmapInfo beatmapInfo; -#nullable enable - [Resolved] - private Bindable? adjustedInfo { get; set; } = null; -#nullable disable + [Resolved(canBeNull: true)] + private Bindable adjustedInfo { get; set; } = null; public IBeatmapInfo BeatmapInfo { @@ -104,19 +102,30 @@ protected override void LoadComplete() private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedStatisticsUpdate; + private StarDifficulty latestStarDifficulty = new StarDifficulty(); private void updateBindedInfo() { if (adjustedInfo == null) return; - BeatmapInfo adjusted = (BeatmapInfo)beatmapInfo; - adjusted.Difficulty.CircleSize = FirstValue.Value.adjustedValue ?? 0; - adjusted.Difficulty.DrainRate = HpDrain.Value.adjustedValue ?? 0; - adjusted.Difficulty.ApproachRate = ApproachRate.Value.adjustedValue ?? 5; - adjusted.Difficulty.OverallDifficulty = Accuracy.Value.adjustedValue ?? 0; - adjusted.StarRating = starDifficulty.Value.adjustedValue ?? 0; + // sadly need to calculate this to prevent additional data transportation + double rate = 1; + foreach (var mod in mods.Value.OfType()) + rate = mod.ApplyToRate(0, rate); + + double bpm = 0; + if (beatmapInfo != null) bpm = beatmapInfo.BPM * rate; + + BeatmapShortInfo adjusted = new BeatmapShortInfo() + { + CircleSize = FirstValue.Value.adjustedValue ?? FirstValue.Value.baseValue, + DrainRate = HpDrain.Value.adjustedValue ?? HpDrain.Value.baseValue, + ApproachRate = ApproachRate.Value.adjustedValue ?? ApproachRate.Value.baseValue, + OverallDifficulty = Accuracy.Value.adjustedValue ?? Accuracy.Value.baseValue, + BPM = bpm, + StarDifficulty = latestStarDifficulty + }; adjustedInfo.Value = adjusted; - adjustedInfo.TriggerChange(); } private void modsChanged(ValueChangedEvent> mods) @@ -165,6 +174,7 @@ private void updateStatistics() Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty); ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); + updateBindedInfo(); // to faster UI response (without SR calculation) updateStarDifficulty(); } @@ -199,6 +209,7 @@ private void updateStarDifficulty() => Scheduler.AddOnce(() => return; starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); + latestStarDifficulty = moddedDifficulty ?? default; updateBindedInfo(); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4567869e8e29..ad98d1721b0d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,7 +100,7 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind private Bindable> selectedMods { get; set; } = null!; [Cached] - private Bindable adjustedInfo { get; set; } = new Bindable(); + private Bindable adjustedInfo { get; set; } = new Bindable(); protected BeatmapCarousel Carousel { get; private set; } = null!; From 0779cd8f4f998c2d9b29fd71787681875bec9979 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 3 Sep 2023 02:17:04 +0300 Subject: [PATCH 109/214] minor design fixes --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 59 ++++++++++++---------- osu.Game/Screens/Select/SongSelect.cs | 4 -- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 426c42454176..8de4447b314e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -222,41 +222,44 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio) }); } - FooterContent.Add(mapInfoContainer = new ModMapInfoContainer + FooterContent.Children = new Drawable[] { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Padding = new MarginPadding + mapInfoContainer = new ModMapInfoContainer { - Vertical = PADDING, - Horizontal = 70 - }, - }); - - FooterContent.Add(footerButtonFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding - { - Vertical = PADDING, - Horizontal = 70 + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Padding = new MarginPadding + { + Vertical = PADDING, + Horizontal = 70 + } }, - Spacing = new Vector2(10), - ChildrenEnumerable = CreateFooterButtons().Prepend(BackButton = new ShearedButton(BUTTON_WIDTH) + footerButtonFlow = new FillFlowContainer { - Text = CommonStrings.Back, - Action = Hide, - DarkerColour = colours.Pink2, - LighterColour = colours.Pink1 - }) - }); + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding + { + Vertical = PADDING, + Horizontal = 70 + }, + Spacing = new Vector2(10), + ChildrenEnumerable = CreateFooterButtons().Prepend(BackButton = new ShearedButton(BUTTON_WIDTH) + { + Text = CommonStrings.Back, + Action = Hide, + DarkerColour = colours.Pink2, + LighterColour = colours.Pink1 + }) + } + }; globalAvailableMods.BindTo(game.AvailableMods); } + public override void Hide() { base.Hide(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ad98d1721b0d..7749e3937a31 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -308,9 +308,6 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog // therein it will be registered at the `OsuGame` level to properly function as a blocking overlay. LoadComponent(ModSelect = CreateModSelectOverlay()); - //var bindedStats = BeatmapDetails.Details.GetBindedAdjustedMapStats(); - //ModSelect.SetBindedMapStats(bindedStats); - if (Footer != null) { foreach (var (button, overlay) in CreateFooterButtons()) @@ -586,7 +583,6 @@ public override void OnEntering(ScreenTransitionEvent e) FilterControl.Activate(); ModSelect.SelectedMods.BindTo(selectedMods); - //BeatmapDetails.AdjustedInfo.BindTo(adjustedInfo); beginLooping(); } From 5e5fe84a88d15af428ee6bff7ecd3e46b26bbf65 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 3 Sep 2023 02:19:02 +0300 Subject: [PATCH 110/214] Update AdvancedStats.cs --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 31fac8b4a05e..a12323ab7017 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -176,7 +176,6 @@ private void updateStatistics() updateBindedInfo(); // to faster UI response (without SR calculation) updateStarDifficulty(); - } private CancellationTokenSource starDifficultyCancellationSource; From d674856e29d230fb6efd6b5a9c0873f6db628050 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Sat, 2 Sep 2023 22:49:29 -0400 Subject: [PATCH 111/214] Use existing localisations in `BeatmapInfoWedge` --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 81759f67872d..8bbf569566fe 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -30,6 +30,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Graphics.Containers; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Screens.Select { @@ -371,7 +372,7 @@ private void addInfoLabels() { new InfoLabel(new BeatmapStatistic { - Name = $"Length (Drain: {playableBeatmap.CalculateDrainLength().ToFormattedDuration().ToString()})", + Name = BeatmapsetsStrings.ShowStatsTotalLength(playableBeatmap.CalculateDrainLength().ToFormattedDuration()), CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(), }), @@ -415,7 +416,7 @@ private void refreshBPMLabel() bpmLabelContainer.Child = new InfoLabel(new BeatmapStatistic { - Name = "BPM", + Name = BeatmapsetsStrings.ShowStatsBpm, CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), Content = labelText }); From 40dbf098d2d31730ddecf38032da48a8d2461910 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Sat, 2 Sep 2023 22:51:08 -0400 Subject: [PATCH 112/214] Use existing localisation for "view profile" --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 3 ++- osu.Game/Users/Drawables/ClickableAvatar.cs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 2d27ce906b9a..40e883f8ac46 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -15,6 +15,7 @@ using osu.Framework.Platform; using osu.Game.Online; using osu.Game.Users; +using osu.Game.Localisation; namespace osu.Game.Graphics.Containers { @@ -74,7 +75,7 @@ public void AddLink(IEnumerable text, LinkAction action, object link } public void AddUserLink(IUser user, Action creationParameters = null) - => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user), "view profile"); + => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user), ContextMenuStrings.ViewProfile); private void createLink(ITextPart textPart, LinkDetails link, LocalisableString tooltipText, Action action = null) { diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index e74ffc9d544f..677a8fff36d0 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -6,14 +6,13 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Users.Drawables { public partial class ClickableAvatar : OsuClickableContainer { - private const string default_tooltip_text = "view profile"; - public override LocalisableString TooltipText { get @@ -21,7 +20,7 @@ public override LocalisableString TooltipText if (!Enabled.Value) return string.Empty; - return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : default_tooltip_text; + return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : ContextMenuStrings.ViewProfile; } set => throw new NotSupportedException(); } From ae9c901b94201de270a3c2fd6ccdbed59107da05 Mon Sep 17 00:00:00 2001 From: Magnus-Cosmos Date: Sun, 3 Sep 2023 01:45:22 -0400 Subject: [PATCH 113/214] Fix `BeatmapInfoWedge` tests failing due to BPM --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index a470ed47d423..7cd4f06bce83 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; @@ -188,7 +189,7 @@ private void checkDisplayedBPM(string target) { AddUntilStep($"displayed bpm is {target}", () => { - var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == "BPM"); + var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsBpm); return label.Statistic.Content == target; }); } From 079792644886a6c57fb03eee397d9594419e5847 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 3 Sep 2023 12:19:03 +0300 Subject: [PATCH 114/214] Update VerticalAttributeDisplay.cs --- osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs index 2ad420657c9a..95d979ebd268 100644 --- a/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs +++ b/osu.Game/Overlays/Mods/VerticalAttributeDisplay.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics.UserInterface; using osuTK; - namespace osu.Game.Overlays.Mods { public partial class VerticalAttributeDisplay : Container, IHasCurrentValue From 8281ed5173af43556f8b2e5ba937a2e66eff7f3a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 3 Sep 2023 14:51:53 +0300 Subject: [PATCH 115/214] Fixed "no animations" issue --- osu.Game/Overlays/Mods/ModMapInfoContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModMapInfoContainer.cs b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs index 378e6f605705..281fe8abe579 100644 --- a/osu.Game/Overlays/Mods/ModMapInfoContainer.cs +++ b/osu.Game/Overlays/Mods/ModMapInfoContainer.cs @@ -50,6 +50,7 @@ public ModMapInfoContainer() const float corner_radius = 7; const float border_thickness = 2; + AutoSizeAxes = Axes.Both; InternalChild = content = new InputBlockingContainer { Origin = Anchor.BottomRight, From b17a55d6a84aba14c6387a03d64053f5b82d4751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Nguy=C3=AAn=20Minh?= Date: Mon, 4 Sep 2023 10:43:05 +0700 Subject: [PATCH 116/214] Add length check for slider velocity --- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 173a665d5cbf..366518eb584d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -177,6 +177,7 @@ private void updateInspectorText() AddHeader("Final velocity"); AddValue($"{beatmapVelocity * current.Value:#,0.00}x"); + if (sliderVelocities.Length == 0) return; if (sliderVelocities.First() != sliderVelocities.Last()) { AddHeader("Beatmap velocity range"); From d5a89c4c45eddff8eb2d88cb7739c08e3260fecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Nguy=C3=AAn=20Minh?= Date: Mon, 4 Sep 2023 13:32:42 +0700 Subject: [PATCH 117/214] Fix formatting --- .../Compose/Components/Timeline/DifficultyPointPiece.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 366518eb584d..99fb2ab87494 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -177,7 +177,11 @@ private void updateInspectorText() AddHeader("Final velocity"); AddValue($"{beatmapVelocity * current.Value:#,0.00}x"); - if (sliderVelocities.Length == 0) return; + if (sliderVelocities.Length == 0) + { + return; + } + if (sliderVelocities.First() != sliderVelocities.Last()) { AddHeader("Beatmap velocity range"); From 0a1ba2ebe08877717f4617cb4d3070ea33d3b3b9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Sep 2023 15:56:32 +0900 Subject: [PATCH 118/214] Remove ModNoMod usage --- .../Mods/TestSceneManiaModDoubleTime.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs index 08e83b04b559..00b79529a9c0 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Tests.Visual; @@ -23,7 +22,6 @@ public partial class TestSceneManiaModDoubleTime : ModTestScene [Test] public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData { - Mod = new ModNoMod(), PassCondition = () => Player.ScoreProcessor.JudgedHits > 0 && Player.ScoreProcessor.Accuracy.Value != 1, Autoplay = false, Beatmap = new Beatmap From 5abf271b56e24adeecffbcb60ca3edd8c62e667c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 3 Sep 2023 21:49:29 -0700 Subject: [PATCH 119/214] Implement beatmap options popover --- .../SongSelect/TestSceneSongSelectFooterV2.cs | 7 +- .../Select/FooterV2/BeatmapOptionsPopover.cs | 148 ++++++++++++++++++ .../Select/FooterV2/FooterButtonOptionsV2.cs | 37 ++++- osu.Game/Screens/Select/FooterV2/FooterV2.cs | 10 +- osu.Game/Screens/Select/SongSelect.cs | 18 +-- 5 files changed, 205 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs index 72adbfc10494..ed2ae67ae59e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -37,10 +38,10 @@ public void SetUp() => Schedule(() => Children = new Drawable[] { - footer = new FooterV2 + new PopoverContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre + RelativeSizeAxes = Axes.Both, + Child = footer = new FooterV2(), }, overlay = new DummyOverlay() }; diff --git a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs new file mode 100644 index 000000000000..ec35c6ff38c8 --- /dev/null +++ b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs @@ -0,0 +1,148 @@ +// 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.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Collections; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Select.FooterV2 +{ + public partial class BeatmapOptionsPopover : OsuPopover + { + private FillFlowContainer buttonFlow = null!; + private readonly FooterButtonOptionsV2 footerButton; + + public BeatmapOptionsPopover(FooterButtonOptionsV2 footerButton) + { + this.footerButton = footerButton; + } + + [BackgroundDependencyLoader] + private void load(ManageCollectionsDialog? manageCollectionsDialog, SongSelect? songSelect, OsuColour colours) + { + Content.Padding = new MarginPadding(5); + + Child = buttonFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(3), + }; + + addButton(@"Manage collections", FontAwesome.Solid.Book, () => manageCollectionsDialog?.Show()); + addButton(@"Delete all difficulties", FontAwesome.Solid.Trash, () => songSelect?.DeleteBeatmap(), colours.Red); + addButton(@"Remove from unplayed", FontAwesome.Regular.TimesCircle, null); + addButton(@"Clear local scores", FontAwesome.Solid.Eraser, () => songSelect?.ClearScores()); + + if (songSelect != null && songSelect.AllowEditing) + addButton(@"Edit beatmap", FontAwesome.Solid.PencilAlt, () => songSelect.Edit()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(this)); + } + + private void addButton(LocalisableString text, IconUsage icon, Action? action, Color4? colour = null) + { + var button = new OptionButton + { + Text = text, + Icon = icon, + TextColour = colour, + Action = () => + { + Hide(); + action?.Invoke(); + }, + }; + + buttonFlow.Add(button); + } + + private partial class OptionButton : OsuButton + { + public IconUsage Icon { get; init; } + public Color4? TextColour { get; init; } + + public OptionButton() + { + Size = new Vector2(265, 50); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundColour = colourProvider.Background3; + + SpriteText.Colour = TextColour ?? Color4.White; + Content.CornerRadius = 10; + + Add(new SpriteIcon + { + Blending = BlendingParameters.Additive, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(17), + X = 15, + Icon = Icon, + Colour = TextColour ?? Color4.White, + }); + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 40 + }; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + // don't absorb control as ToolbarRulesetSelector uses control + number to navigate + if (e.ControlPressed) return false; + + if (!e.Repeat && e.Key >= Key.Number1 && e.Key <= Key.Number9) + { + int requested = e.Key - Key.Number1; + + OptionButton? found = buttonFlow.Children.ElementAtOrDefault(requested); + + if (found != null) + { + found.TriggerClick(); + return true; + } + } + + return base.OnKeyDown(e); + } + + protected override void UpdateState(ValueChangedEvent state) + { + base.UpdateState(state); + + if (state.NewValue == Visibility.Hidden) + footerButton.IsActive.Value = false; + } + } +} diff --git a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs index 87cca0042ab0..a1559d32dc96 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs @@ -2,14 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select.FooterV2 { - public partial class FooterButtonOptionsV2 : FooterButtonV2 + public partial class FooterButtonOptionsV2 : FooterButtonV2, IHasPopover { + public readonly BindableBool IsActive = new BindableBool(); + [BackgroundDependencyLoader] private void load(OsuColour colour) { @@ -17,6 +24,34 @@ private void load(OsuColour colour) Icon = FontAwesome.Solid.Cog; AccentColour = colour.Purple1; Hotkey = GlobalAction.ToggleBeatmapOptions; + + Action = () => IsActive.Toggle(); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsActive.BindValueChanged(active => + { + OverlayState.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; + }); + + OverlayState.BindValueChanged(state => + { + switch (state.NewValue) + { + case Visibility.Hidden: + this.HidePopover(); + break; + + case Visibility.Visible: + this.ShowPopover(); + break; + } + }); + } + + public Popover GetPopover() => new BeatmapOptionsPopover(this); } } diff --git a/osu.Game/Screens/Select/FooterV2/FooterV2.cs b/osu.Game/Screens/Select/FooterV2/FooterV2.cs index cd95f3eb6ccd..0529f0d08265 100644 --- a/osu.Game/Screens/Select/FooterV2/FooterV2.cs +++ b/osu.Game/Screens/Select/FooterV2/FooterV2.cs @@ -48,11 +48,17 @@ private void showOverlay(OverlayContainer overlay) private FillFlowContainer buttons = null!; - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + public FooterV2() { RelativeSizeAxes = Axes.X; Height = height; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { InternalChildren = new Drawable[] { new Box diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 58755878d0d5..4ce7a6167e03 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -311,9 +311,9 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog Footer.AddButton(button, overlay); BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show()); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, DeleteBeatmap); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo)); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, ClearScores); } sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); @@ -916,18 +916,18 @@ private bool transferRulesetValue() return true; } - private void delete(BeatmapSetInfo? beatmap) + public void DeleteBeatmap() { - if (beatmap == null) return; + if (Beatmap.Value.BeatmapSetInfo == null) return; - dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); + dialogOverlay?.Push(new BeatmapDeleteDialog(Beatmap.Value.BeatmapSetInfo)); } - private void clearScores(BeatmapInfo? beatmapInfo) + public void ClearScores() { - if (beatmapInfo == null) return; + if (Beatmap.Value.BeatmapInfo == null) return; - dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmapInfo, () => + dialogOverlay?.Push(new BeatmapClearScoresDialog(Beatmap.Value.BeatmapInfo, () => // schedule done here rather than inside the dialog as the dialog may fade out and never callback. Schedule(() => BeatmapDetails.Refresh()))); } @@ -963,7 +963,7 @@ protected override bool OnKeyDown(KeyDownEvent e) if (e.ShiftPressed) { if (!Beatmap.IsDefault) - delete(Beatmap.Value.BeatmapSetInfo); + DeleteBeatmap(); return true; } From 6c0bd13308589a8efcc2ba83738bc6ed0c2c6ca9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 00:21:48 -0700 Subject: [PATCH 120/214] Add xmldoc to newly exposed methods --- osu.Game/Screens/Select/SongSelect.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4ce7a6167e03..f6fc55b2a544 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -916,6 +916,9 @@ private bool transferRulesetValue() return true; } + /// + /// Request to delete the current beatmap. + /// public void DeleteBeatmap() { if (Beatmap.Value.BeatmapSetInfo == null) return; @@ -923,6 +926,9 @@ public void DeleteBeatmap() dialogOverlay?.Push(new BeatmapDeleteDialog(Beatmap.Value.BeatmapSetInfo)); } + /// + /// Request to clear the scores of the current beatmap. + /// public void ClearScores() { if (Beatmap.Value.BeatmapInfo == null) return; From f616648730ac9fd438cc2d15aeb7eb065f875cae Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 09:40:35 -0700 Subject: [PATCH 121/214] Remove icon blending --- osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs index ec35c6ff38c8..4e1334fd1153 100644 --- a/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/Select/FooterV2/BeatmapOptionsPopover.cs @@ -97,7 +97,6 @@ private void load(OverlayColourProvider colourProvider) Add(new SpriteIcon { - Blending = BlendingParameters.Additive, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(17), From bf71099e5743efb966482d5b145a551b0208729f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 11:34:21 -0700 Subject: [PATCH 122/214] Fix truncating sprite text usage --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index b7b60cffab6f..7821aa5be0c8 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -280,15 +280,14 @@ private void load(LocalisationManager localisation) RelativeSizeAxes = Axes.X, Children = new Drawable[] { - TitleLabel = new OsuSpriteText + TitleLabel = new TruncatingSpriteText { Shadow = true, Current = { BindTarget = titleBinding }, Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true }, - ArtistLabel = new OsuSpriteText + ArtistLabel = new TruncatingSpriteText { // TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware. Shadow = true, @@ -296,7 +295,6 @@ private void load(LocalisationManager localisation) // Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true } } } From e8a793425bf28e19ba3667c8c712c4db96fc09fa Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:38:40 -0700 Subject: [PATCH 123/214] Use right padding instead of negative x offset --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 7821aa5be0c8..284f14cd9eac 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -155,18 +155,21 @@ private void updateDisplay() LoadComponentAsync(loadingInfo = new Container { - Masking = true, - // We offset this by the portion of the colour bar underneath we wish to show - X = -colour_bar_width, - CornerRadius = corner_radius, + Padding = new MarginPadding { Right = colour_bar_width }, RelativeSizeAxes = Axes.Both, Depth = DisplayedContent?.Depth + 1 ?? 0, - Children = new Drawable[] + Child = new Container { - // TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft. - // pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered. - new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, - Info = new WedgeInfoText(beatmap) { Shear = -Shear } + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + // TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft. + // pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered. + new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear }, + Info = new WedgeInfoText(beatmap) { Shear = -Shear } + } } }, loaded => { From 2df20027355ddbf24bd43401ef5feabf5a7e1823 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:42:01 -0700 Subject: [PATCH 124/214] Move negative corner radius margin to constructor --- .../SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 16 ++++++++++------ osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 09b93119cc20..ae5b739c4dc7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -50,13 +50,17 @@ protected override void LoadComplete() RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Top = 20, Left = -10 } }, - infoWedge = new TestBeatmapInfoWedgeV2 + new Container { - State = { Value = Visibility.Visible }, - Width = 0.6f, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20, Left = -10 } - }, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 20 }, + Child = infoWedge = new TestBeatmapInfoWedgeV2 + { + State = { Value = Visibility.Visible }, + Width = 0.6f, + RelativeSizeAxes = Axes.X, + }, + } }); AddSliderStep("change star difficulty", 0, 11.9, 5.55, v => diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 284f14cd9eac..742d9011b97e 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -55,6 +55,7 @@ public BeatmapInfoWedgeV2() Height = WEDGE_HEIGHT; Shear = wedged_container_shear; Masking = true; + Margin = new MarginPadding { Left = -corner_radius }; EdgeEffect = new EdgeEffectParameters { Colour = Colour4.Black.Opacity(0.2f), From e70510ef191ef345eee35b490a12118fbb29a755 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:56:20 -0700 Subject: [PATCH 125/214] Move drawable and binding logic to standard places --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 742d9011b97e..de3e634819f2 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -46,9 +46,9 @@ public partial class BeatmapInfoWedgeV2 : VisibilityContainer protected WedgeInfoText? Info { get; private set; } - private readonly Container difficultyColourBar; - private readonly StarCounter starCounter; - private readonly BufferedContainer bufferedContent; + private Container difficultyColourBar = null!; + private StarCounter starCounter = null!; + private BufferedContainer bufferedContent = null!; public BeatmapInfoWedgeV2() { @@ -63,7 +63,11 @@ public BeatmapInfoWedgeV2() Radius = 3, }; CornerRadius = corner_radius; + } + [BackgroundDependencyLoader] + private void load() + { // We want to buffer the wedge to avoid weird transparency overlaps between the colour bar and the background. Child = bufferedContent = new BufferedContainer(pixelSnapping: true) { @@ -107,9 +111,10 @@ public BeatmapInfoWedgeV2() }; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { + base.LoadComplete(); + ruleset.BindValueChanged(_ => updateDisplay()); } @@ -228,6 +233,8 @@ public partial class WedgeInfoText : Container public WedgeInfoText(WorkingBeatmap working) { this.working = working; + + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -236,8 +243,6 @@ private void load(LocalisationManager localisation) var beatmapInfo = working.BeatmapInfo; var metadata = working.Metadata; - RelativeSizeAxes = Axes.Both; - titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); From 82fb9dc2ef5eb03f2db25426f11a19f836e62f68 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 12:59:21 -0700 Subject: [PATCH 126/214] Simplify text initialization --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index de3e634819f2..fd655c50ca5c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -212,9 +212,6 @@ public partial class WedgeInfoText : Container private StarRatingDisplay starRatingDisplay = null!; - private ILocalisedBindableString titleBinding = null!; - private ILocalisedBindableString artistBinding = null!; - private readonly WorkingBeatmap working; public IBindable DisplayedStars => starRatingDisplay.DisplayedStars; @@ -238,14 +235,11 @@ public WedgeInfoText(WorkingBeatmap working) } [BackgroundDependencyLoader] - private void load(LocalisationManager localisation) + private void load() { var beatmapInfo = working.BeatmapInfo; var metadata = working.Metadata; - titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); - artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); - Children = new Drawable[] { new FillFlowContainer @@ -292,7 +286,7 @@ private void load(LocalisationManager localisation) TitleLabel = new TruncatingSpriteText { Shadow = true, - Current = { BindTarget = titleBinding }, + Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, }, @@ -300,7 +294,7 @@ private void load(LocalisationManager localisation) { // TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware. Shadow = true, - Current = { BindTarget = artistBinding }, + Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist), // Not sure if this should be semi bold or medium Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, From e0a9c7e9a9680004b98a8086354fb758b65daa51 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 14:54:58 -0700 Subject: [PATCH 127/214] Fix wedge showing abruptly in test --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index ae5b739c4dc7..3236841dc3a9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -56,7 +56,6 @@ protected override void LoadComplete() Padding = new MarginPadding { Top = 20 }, Child = infoWedge = new TestBeatmapInfoWedgeV2 { - State = { Value = Visibility.Visible }, Width = 0.6f, RelativeSizeAxes = Axes.X, }, @@ -165,6 +164,7 @@ private void selectBeatmap(IBeatmap? b) { containerBefore = infoWedge.DisplayedContent; infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); + infoWedge.Show(); }); AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); From 854bb323cc09e0a296ebd2ca948e4a6074950044 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 15:02:00 -0700 Subject: [PATCH 128/214] Remove weird red edge effect visibility --- .../Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 3236841dc3a9..827d23c0fc0b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; @@ -94,17 +93,6 @@ public void TestRulesetChange() [Test] public void TestWedgeVisibility() { - // Mostly just in case someone runs this test before others, - // leading to the shadow being very hard to see if it is black - AddStep("make shadow red for test visibility", () => - { - infoWedge.EdgeEffect = new EdgeEffectParameters - { - Colour = Colour4.Red, - Type = EdgeEffectType.Shadow, - Radius = 5, - }; - }); AddStep("hide", () => { infoWedge.Hide(); }); AddWaitStep("wait for hide", 3); AddAssert("check visibility", () => infoWedge.Alpha == 0); From 9accd0ded262e451792a10013762e442b5523441 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 15:02:38 -0700 Subject: [PATCH 129/214] Fix star rating rolling counter regression --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index fd655c50ca5c..5316b4620b47 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -314,7 +314,7 @@ protected override void LoadComplete() starRatingDisplay.Current.Value = s.NewValue ?? default; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) - if (starRatingDisplay.Alpha > 0) + if (!starRatingDisplay.IsPresent) starRatingDisplay.FinishTransforms(true); starRatingDisplay.FadeIn(transition_duration); From 98d027d207143532178e4588c279368abb391d8b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 21:34:53 -0700 Subject: [PATCH 130/214] Fix star counter animating weird and delayed --- osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs index 5316b4620b47..de930ff83750 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs @@ -187,11 +187,16 @@ private void updateDisplay() Info.DisplayedStars.BindValueChanged(s => { - starCounter.Current = (float)s.NewValue; starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f); difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue)); }, true); + + Info.ActualStars.BindValueChanged(s => + { + // use actual stars as star counter has its own animation + starCounter.Current = (float)s.NewValue; + }); }); }); @@ -215,6 +220,7 @@ public partial class WedgeInfoText : Container private readonly WorkingBeatmap working; public IBindable DisplayedStars => starRatingDisplay.DisplayedStars; + public Bindable ActualStars = new Bindable(); [Resolved] private IBindable> mods { get; set; } = null!; @@ -312,6 +318,7 @@ protected override void LoadComplete() starDifficulty.BindValueChanged(s => { starRatingDisplay.Current.Value = s.NewValue ?? default; + ActualStars.Value = s.NewValue?.Stars ?? 0; // Don't roll the counter on initial display (but still allow it to roll on applying mods etc.) if (!starRatingDisplay.IsPresent) From 94516133912a4c32048dddc52c53fb9696bb0477 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Sep 2023 21:39:26 -0700 Subject: [PATCH 131/214] Rename null beatmap test to indicate there's a background --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs index 827d23c0fc0b..a8484caf1c6f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs @@ -120,7 +120,7 @@ public void TestTruncation() } [Test] - public void TestNullBeatmap() + public void TestNullBeatmapWithBackground() { selectBeatmap(null); AddAssert("check default title", () => infoWedge.Info!.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); From 1215ad7ace400cb404121d9a8ead9ac81f2ad2a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Sep 2023 15:40:00 +0900 Subject: [PATCH 132/214] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2d15bce85a2c..2bfdce5ab8dd 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - +