From ac91f0e2707bf57eacacf079c4de09b1c108926d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Dec 2020 00:25:46 +0900 Subject: [PATCH 001/101] Add extended limits to difficulty adjustment mod --- .../Mods/CatchModDifficultyAdjust.cs | 12 ++- .../Mods/OsuModDifficultyAdjust.cs | 12 ++- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 79 ++++++++++++++++++- 3 files changed, 97 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index acdd0a420c..859dfb7647 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloat + public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 1, @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Mods }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloat + public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 1, @@ -31,6 +31,14 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; + protected override void ApplyLimits(bool extended) + { + base.ApplyLimits(extended); + + CircleSize.MaxValue = extended ? 11 : 10; + ApproachRate.MaxValue = extended ? 11 : 10; + } + public override string SettingDescription { get diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index ff995e38ce..a6ad2e75f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloat + public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloat + public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -31,6 +31,14 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; + protected override void ApplyLimits(bool extended) + { + base.ApplyLimits(extended); + + CircleSize.MaxValue = extended ? 11 : 10; + ApproachRate.MaxValue = extended ? 11 : 10; + } + public override string SettingDescription { get diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 165644edbe..7df663ad3a 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] - public BindableNumber DrainRate { get; } = new BindableFloat + public BindableNumber DrainRate { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mods }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] - public BindableNumber OverallDifficulty { get; } = new BindableFloat + public BindableNumber OverallDifficulty { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -53,6 +53,24 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; + [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] + public BindableBool ExtendedLimits { get; } = new BindableBool(); + + protected ModDifficultyAdjust() + { + ExtendedLimits.BindValueChanged(extend => ApplyLimits(extend.NewValue)); + } + + /// + /// Changes the difficulty adjustment limits. Occurs when the value of is changed. + /// + /// Whether limits should extend beyond sane ranges. + protected virtual void ApplyLimits(bool extended) + { + DrainRate.MaxValue = extended ? 11 : 10; + OverallDifficulty.MaxValue = extended ? 11 : 10; + } + public override string SettingDescription { get @@ -123,5 +141,62 @@ namespace osu.Game.Rulesets.Mods difficulty.DrainRate = DrainRate.Value; difficulty.OverallDifficulty = OverallDifficulty.Value; } + + /// + /// A that extends its min/max values to support any assigned value. + /// + protected class BindableDoubleWithLimitExtension : BindableDouble + { + public override double Value + { + get => base.Value; + set + { + if (value < MinValue) + MinValue = value; + if (value > MaxValue) + MaxValue = value; + base.Value = value; + } + } + } + + /// + /// A that extends its min/max values to support any assigned value. + /// + protected class BindableFloatWithLimitExtension : BindableFloat + { + public override float Value + { + get => base.Value; + set + { + if (value < MinValue) + MinValue = value; + if (value > MaxValue) + MaxValue = value; + base.Value = value; + } + } + } + + /// + /// A that extends its min/max values to support any assigned value. + /// + protected class BindableIntWithLimitExtension : BindableInt + { + public override int Value + { + get => base.Value; + set + { + if (value < MinValue) + MinValue = value; + if (value > MaxValue) + MaxValue = value; + base.Value = value; + } + } + } } } From 47a93d8614eff32b5fd7bb9db8b81e8d97a47f34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Dec 2020 00:26:35 +0900 Subject: [PATCH 002/101] Adjust osu! hitobject fade-ins to support AR>10 --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 5 ++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 6e7b1050cb..40154ca84c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -110,8 +110,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double startTime = start.GetEndTime(); double duration = end.StartTime - startTime; + // For now, adjust the pre-empt for approach rates > 10. + double preempt = PREEMPT * Math.Min(1, start.TimePreempt / 450); + fadeOutTime = startTime + fraction * duration; - fadeInTime = fadeOutTime - PREEMPT; + fadeInTime = fadeOutTime - preempt; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 15af141c99..6d28a576a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - TimeFadeIn = 400; // as per osu-stable + TimeFadeIn = 400 * Math.Min(1, TimePreempt / 450); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } From 9835245ea29e3dcc053feb818b65f2e959cb5d06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Dec 2020 00:32:31 +0900 Subject: [PATCH 003/101] Add test --- .../Online/TestAPIModSerialization.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Online/TestAPIModSerialization.cs b/osu.Game.Tests/Online/TestAPIModSerialization.cs index 5948582d77..84862ebb07 100644 --- a/osu.Game.Tests/Online/TestAPIModSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModSerialization.cs @@ -68,12 +68,29 @@ namespace osu.Game.Tests.Online Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); } + [Test] + public void TestDeserialiseDifficultyAdjustModWithExtendedLimits() + { + var apiMod = new APIMod(new TestModDifficultyAdjust + { + OverallDifficulty = { Value = 11 }, + ExtendedLimits = { Value = true } + }); + + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + var converted = (TestModDifficultyAdjust)deserialised.ToMod(new TestRuleset()); + + Assert.That(converted.ExtendedLimits.Value, Is.True); + Assert.That(converted.OverallDifficulty.Value, Is.EqualTo(11)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => new Mod[] { new TestMod(), new TestModTimeRamp(), + new TestModDifficultyAdjust() }; public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); @@ -135,5 +152,9 @@ namespace osu.Game.Tests.Online Value = true }; } + + private class TestModDifficultyAdjust : ModDifficultyAdjust + { + } } } From 27ffc9844520036e81a652d9d8d7b45b7b5845be Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Jan 2021 10:48:12 +0300 Subject: [PATCH 004/101] Implement WebOverlay component --- .../Online/TestSceneFullscreenOverlay.cs | 4 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 86 ++++++++----------- osu.Game/Overlays/BeatmapSetOverlay.cs | 74 ++++++---------- osu.Game/Overlays/ChangelogOverlay.cs | 56 +++--------- osu.Game/Overlays/DashboardOverlay.cs | 56 ++---------- osu.Game/Overlays/FullscreenOverlay.cs | 26 +++++- osu.Game/Overlays/NewsOverlay.cs | 62 +++---------- osu.Game/Overlays/RankingsOverlay.cs | 81 +++-------------- osu.Game/Overlays/UserProfileOverlay.cs | 13 ++- osu.Game/Overlays/WebOverlay.cs | 50 +++++++++++ 10 files changed, 193 insertions(+), 315 deletions(-) create mode 100644 osu.Game/Overlays/WebOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs index 8f20bcdcc1..f8b059e471 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online private class TestFullscreenOverlay : FullscreenOverlay { public TestFullscreenOverlay() - : base(OverlayColourScheme.Pink, null) + : base(OverlayColourScheme.Pink) { Children = new Drawable[] { @@ -52,6 +52,8 @@ namespace osu.Game.Tests.Visual.Online }, }; } + + protected override OverlayHeader CreateHeader() => null; } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c9c995dd6..ae1667d403 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,98 +15,82 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapListingOverlay : FullscreenOverlay + public class BeatmapListingOverlay : WebOverlay { [Resolved] private PreviewTrackManager previewTrackManager { get; set; } private Drawable currentContent; - private LoadingLayer loadingLayer; private Container panelTarget; private FillFlowContainer foundContent; private NotFoundDrawable notFoundContent; - - private OverlayScrollContainer resultScrollContainer; + private BeatmapListingFilterControl filterControl; public BeatmapListingOverlay() - : base(OverlayColourScheme.Blue, new BeatmapListingHeader()) + : base(OverlayColourScheme.Blue) { } - private BeatmapListingFilterControl filterControl; - [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Child = new FillFlowContainer { - new Box + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background6 - }, - resultScrollContainer = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer + filterControl = new BeatmapListingFilterControl + { + TypingStarted = onTypingStarted, + SearchStarted = onSearchStarted, + SearchFinished = onSearchFinished, + }, + new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, Children = new Drawable[] { - Header, - filterControl = new BeatmapListingFilterControl + new Box { - TypingStarted = onTypingStarted, - SearchStarted = onSearchStarted, - SearchFinished = onSearchFinished, + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background4, }, - new Container + panelTarget = new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background4, - }, - panelTarget = new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 20 }, - Children = new Drawable[] - { - foundContent = new FillFlowContainer(), - notFoundContent = new NotFoundDrawable(), - } - } - }, - }, - } + foundContent = new FillFlowContainer(), + notFoundContent = new NotFoundDrawable(), + } + } + }, }, - }, - loadingLayer = new LoadingLayer(true) + } }; } + protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader(); + + protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + private void onTypingStarted() { // temporary until the textbox/header is updated to always stay on screen. - resultScrollContainer.ScrollToStart(); + ScrollFlow.ScrollToStart(); } protected override void OnFocus(FocusEvent e) @@ -125,7 +109,7 @@ namespace osu.Game.Overlays previewTrackManager.StopAnyPlaying(this); if (panelTarget.Any()) - loadingLayer.Show(); + Loading.Show(); } private Task panelLoadDelegate; @@ -173,7 +157,7 @@ namespace osu.Game.Overlays private void addContentToPlaceholder(Drawable content) { - loadingLayer.Hide(); + Loading.Hide(); lastFetchDisplayedTime = Time.Current; var lastContent = currentContent; @@ -256,7 +240,7 @@ namespace osu.Game.Overlays bool shouldShowMore = panelLoadDelegate?.IsCompleted != false && Time.Current - lastFetchDisplayedTime > time_between_fetches - && (resultScrollContainer.ScrollableExtent > 0 && resultScrollContainer.IsScrolledToEnd(pagination_scroll_distance)); + && (ScrollFlow.ScrollableExtent > 0 && ScrollFlow.IsScrolledToEnd(pagination_scroll_distance)); if (shouldShowMore) filterControl.FetchNextPage(); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index bbec62a85a..10953415ed 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; @@ -16,10 +15,11 @@ using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; using osu.Game.Rulesets; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : FullscreenOverlay // we don't provide a standard header for now. + public class BeatmapSetOverlay : WebOverlay // we don't provide a standard header for now. { public const float X_PADDING = 40; public const float Y_PADDING = 25; @@ -36,55 +36,40 @@ namespace osu.Game.Overlays // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - private readonly Box background; - public BeatmapSetOverlay() - : base(OverlayColourScheme.Blue, null) + : base(OverlayColourScheme.Blue) { - OverlayScrollContainer scroll; Info info; CommentsSection comments; - Children = new Drawable[] + Child = new FillFlowContainer { - background = new Box + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }, - scroll = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer + new BeatmapSetLayoutSection { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Children = new[] + Child = new ReverseChildIDFillFlowContainer { - new BeatmapSetLayoutSection + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Child = new ReverseChildIDFillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header = new Header(), - info = new Info() - } - }, - }, - new ScoresContainer - { - Beatmap = { BindTarget = Header.Picker.Beatmap } - }, - comments = new CommentsSection() + Header = new Header(), + info = new Info() + } }, }, - }, + new ScoresContainer + { + Beatmap = { BindTarget = Header.Picker.Beatmap } + }, + comments = new CommentsSection() + } }; Header.BeatmapSet.BindTo(beatmapSet); @@ -94,16 +79,13 @@ namespace osu.Game.Overlays Header.Picker.Beatmap.ValueChanged += b => { info.Beatmap = b.NewValue; - - scroll.ScrollToStart(); + ScrollFlow.ScrollToStart(); }; } - [BackgroundDependencyLoader] - private void load() - { - background.Colour = ColourProvider.Background6; - } + protected override OverlayHeader CreateHeader() => null; + + protected override Color4 GetBackgroundColour() => ColourProvider.Background6; protected override void PopOutComplete() { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index c7e9a86fa4..5d99887053 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -11,22 +11,18 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Changelog; +using osuTK.Graphics; namespace osu.Game.Overlays { - public class ChangelogOverlay : FullscreenOverlay + public class ChangelogOverlay : WebOverlay { public readonly Bindable Current = new Bindable(); - private Container content; - private SampleChannel sampleBack; private List builds; @@ -34,45 +30,14 @@ namespace osu.Game.Overlays protected List Streams; public ChangelogOverlay() - : base(OverlayColourScheme.Purple, new ChangelogHeader()) + : base(OverlayColourScheme.Purple) { } [BackgroundDependencyLoader] private void load(AudioManager audio) { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background4, - }, - new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header.With(h => - { - h.ListingSelected = ShowListing; - h.Build.BindTarget = Current; - }), - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }, - }, - }, - }; + Header.Build.BindTarget = Current; sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); @@ -85,6 +50,13 @@ namespace osu.Game.Overlays }); } + protected override ChangelogHeader CreateHeader() => new ChangelogHeader + { + ListingSelected = ShowListing, + }; + + protected override Color4 GetBackgroundColour() => ColourProvider.Background4; + public void ShowListing() { Current.Value = null; @@ -198,16 +170,16 @@ namespace osu.Game.Overlays private void loadContent(ChangelogContent newContent) { - content.FadeTo(0.2f, 300, Easing.OutQuint); + Content.FadeTo(0.2f, 300, Easing.OutQuint); loadContentCancellation?.Cancel(); LoadComponentAsync(newContent, c => { - content.FadeIn(300, Easing.OutQuint); + Content.FadeIn(300, Easing.OutQuint); c.BuildSelected = ShowBuild; - content.Child = c; + Child = c; }, (loadContentCancellation = new CancellationTokenSource()).Token); } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 03c320debe..3722d36388 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -7,29 +7,18 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Dashboard; using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : FullscreenOverlay + public class DashboardOverlay : WebOverlay { private CancellationTokenSource cancellationToken; - private Container content; - private LoadingLayer loading; - private OverlayScrollContainer scrollFlow; - public DashboardOverlay() - : base(OverlayColourScheme.Purple, new DashboardOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }) + : base(OverlayColourScheme.Purple) { } @@ -40,45 +29,16 @@ namespace osu.Game.Overlays { apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5 - }, - scrollFlow = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header, - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - } - } - } - }, - loading = new LoadingLayer(true), - }; } protected override void LoadComplete() { base.LoadComplete(); - Header.Current.BindValueChanged(onTabChanged); } + protected override DashboardOverlayHeader CreateHeader() => new DashboardOverlayHeader(); + private bool displayUpdateRequired = true; protected override void PopIn() @@ -102,21 +62,21 @@ namespace osu.Game.Overlays private void loadDisplay(Drawable display) { - scrollFlow.ScrollToStart(); + ScrollFlow.ScrollToStart(); LoadComponentAsync(display, loaded => { if (API.IsLoggedIn) - loading.Hide(); + Loading.Hide(); - content.Child = loaded; + Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); } private void onTabChanged(ValueChangedEvent tab) { cancellationToken?.Cancel(); - loading.Show(); + Loading.Show(); if (!API.IsLoggedIn) { diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 6f56d95929..d65213f573 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osuTK.Graphics; @@ -27,9 +28,13 @@ namespace osu.Game.Overlays [Cached] protected readonly OverlayColourProvider ColourProvider; - protected FullscreenOverlay(OverlayColourScheme colourScheme, T header) + protected override Container Content => content; + + private readonly Container content; + + protected FullscreenOverlay(OverlayColourScheme colourScheme) { - Header = header; + Header = CreateHeader(); ColourProvider = new OverlayColourProvider(colourScheme); @@ -47,6 +52,19 @@ namespace osu.Game.Overlays Type = EdgeEffectType.Shadow, Radius = 10 }; + + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = GetBackgroundColour() + }, + content = new Container + { + RelativeSizeAxes = Axes.Both + } + }); } [BackgroundDependencyLoader] @@ -58,6 +76,10 @@ namespace osu.Game.Overlays Waves.FourthWaveColour = ColourProvider.Dark3; } + protected abstract T CreateHeader(); + + protected virtual Color4 GetBackgroundColour() => ColourProvider.Background5; + public override void Show() { if (State.Value == Visibility.Visible) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 5820d405d4..268d2bb6a2 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -2,67 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.News; using osu.Game.Overlays.News.Displays; namespace osu.Game.Overlays { - public class NewsOverlay : FullscreenOverlay + public class NewsOverlay : WebOverlay { private readonly Bindable article = new Bindable(null); - private Container content; - private LoadingLayer loading; - private OverlayScrollContainer scrollFlow; - public NewsOverlay() - : base(OverlayColourScheme.Purple, new NewsHeader()) + : base(OverlayColourScheme.Purple) { } - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5, - }, - scrollFlow = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header.With(h => - { - h.ShowFrontPage = ShowFrontPage; - }), - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }, - }, - }, - loading = new LoadingLayer(true), - }; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -71,6 +26,11 @@ namespace osu.Game.Overlays article.BindValueChanged(onArticleChanged); } + protected override NewsHeader CreateHeader() => new NewsHeader + { + ShowFrontPage = ShowFrontPage + }; + private bool displayUpdateRequired = true; protected override void PopIn() @@ -107,7 +67,7 @@ namespace osu.Game.Overlays private void onArticleChanged(ValueChangedEvent e) { cancellationToken?.Cancel(); - loading.Show(); + Loading.Show(); if (e.NewValue == null) { @@ -122,11 +82,11 @@ namespace osu.Game.Overlays protected void LoadDisplay(Drawable display) { - scrollFlow.ScrollToStart(); + ScrollFlow.ScrollToStart(); LoadComponentAsync(display, loaded => { - content.Child = loaded; - loading.Hide(); + Child = loaded; + Loading.Hide(); }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 25350e310a..89853e9044 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -4,96 +4,41 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Overlays.Rankings; using osu.Game.Users; using osu.Game.Rulesets; using osu.Game.Online.API; using System.Threading; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : FullscreenOverlay + public class RankingsOverlay : WebOverlay { protected Bindable Country => Header.Country; protected Bindable Scope => Header.Current; - private readonly OverlayScrollContainer scrollFlow; - private readonly Container contentContainer; - private readonly LoadingLayer loading; - private readonly Box background; - private APIRequest lastRequest; private CancellationTokenSource cancellationToken; [Resolved] private IAPIProvider api { get; set; } - public RankingsOverlay() - : base(OverlayColourScheme.Green, new RankingsOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }) - { - loading = new LoadingLayer(true); + [Resolved] + private Bindable ruleset { get; set; } - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - scrollFlow = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - contentContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Bottom = 10 } - }, - } - } - } - } - }, - loading - }; + public RankingsOverlay() + : base(OverlayColourScheme.Green) + { } [BackgroundDependencyLoader] private void load() { - background.Colour = ColourProvider.Background5; } - [Resolved] - private Bindable ruleset { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -129,6 +74,8 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); } + protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); + public void ShowCountry(Country requested) { if (requested == null) @@ -147,7 +94,7 @@ namespace osu.Game.Overlays private void loadNewContent() { - loading.Show(); + Loading.Show(); cancellationToken?.Cancel(); lastRequest?.Cancel(); @@ -218,19 +165,19 @@ namespace osu.Game.Overlays private void loadContent(Drawable content) { - scrollFlow.ScrollToStart(); + ScrollFlow.ScrollToStart(); if (content == null) { - contentContainer.Clear(); - loading.Hide(); + Clear(); + Loading.Hide(); return; } LoadComponentAsync(content, loaded => { - loading.Hide(); - contentContainer.Child = loaded; + Loading.Hide(); + Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 81027667fa..c29df72501 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Sections; using osu.Game.Users; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -29,10 +30,14 @@ namespace osu.Game.Overlays public const float CONTENT_X_MARGIN = 70; public UserProfileOverlay() - : base(OverlayColourScheme.Pink, new ProfileHeader()) + : base(OverlayColourScheme.Pink) { } + protected override ProfileHeader CreateHeader() => new ProfileHeader(); + + protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + public void ShowUser(int userId) => ShowUser(new User { Id = userId }); public void ShowUser(User user, bool fetchOnline = true) @@ -72,12 +77,6 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre, }; - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background6 - }); - Add(sectionsContainer = new ProfileSectionsContainer { ExpandableHeader = Header, diff --git a/osu.Game/Overlays/WebOverlay.cs b/osu.Game/Overlays/WebOverlay.cs new file mode 100644 index 0000000000..aca767e32f --- /dev/null +++ b/osu.Game/Overlays/WebOverlay.cs @@ -0,0 +1,50 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays +{ + public abstract class WebOverlay : FullscreenOverlay + where T : OverlayHeader + { + protected override Container Content => content; + + protected readonly OverlayScrollContainer ScrollFlow; + protected readonly LoadingLayer Loading; + private readonly Container content; + + protected WebOverlay(OverlayColourScheme colourScheme) + : base(colourScheme) + { + FillFlowContainer flow = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical + }; + + if (Header != null) + flow.Add(Header.With(h => h.Depth = -float.MaxValue)); + + flow.Add(content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }); + + base.Content.AddRange(new Drawable[] + { + ScrollFlow = new OverlayScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = flow + }, + Loading = new LoadingLayer(true) + }); + } + } +} From 88abee705b37d0a6c9db00aa7b8a3f5323703590 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 10:48:53 +0300 Subject: [PATCH 005/101] Add missing event mapping for user beatmap availability change --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 50dc8f661c..6f789b1ef5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -81,6 +81,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); + connection.On(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged); connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); From 4e6c1a3906ed40be5752b13994499f73b4c10142 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 10:49:38 +0300 Subject: [PATCH 006/101] Update client beatmap availability in-line with tracker --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 ++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c049d4be20..7569da00f2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -43,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected readonly MultiplayerBeatmapTracker BeatmapTracker; + protected IBindable BeatmapAvailability => BeatmapTracker.Availability; + protected RoomSubScreen() { InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a641935b9a..94288673fb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -184,7 +184,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); + BeatmapAvailability.BindValueChanged(updateClientAvailability, true); + client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; isConnected = client.IsConnected.GetBoundCopy(); @@ -208,6 +210,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault(); + private void updateClientAvailability(ValueChangedEvent _ = null) + { + if (client.Room != null) + client.ChangeBeatmapAvailability(BeatmapAvailability.Value).CatchUnobservedExceptions(true); + } + + private void onRoomUpdated() + { + if (client.Room == null) + return; + + if (client.LocalUser?.BeatmapAvailability.Equals(BeatmapAvailability.Value) == false) + updateClientAvailability(); + } + private void onReadyClick() { Debug.Assert(readyClickOperation == null); @@ -262,7 +279,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.Dispose(isDisposing); if (client != null) + { client.LoadRequested -= onLoadRequested; + client.RoomUpdated -= onRoomUpdated; + } } } } From bd44bf8c0b9348f9443df2dfc95470d86bd80ecf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 10:46:48 +0300 Subject: [PATCH 007/101] Extract disabling progress bar user-interactivity --- osu.Game/Graphics/UserInterface/ProgressBar.cs | 8 +++++++- .../Overlays/BeatmapListing/Panels/DownloadProgressBar.cs | 8 +------- osu.Game/Overlays/NowPlayingOverlay.cs | 5 +++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index d271cd121c..4ee1c73bf5 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -40,8 +40,14 @@ namespace osu.Game.Graphics.UserInterface set => CurrentNumber.Value = value; } - public ProgressBar() + private readonly bool userInteractive; + public override bool HandlePositionalInput => userInteractive; + public override bool HandleNonPositionalInput => userInteractive; + + public ProgressBar(bool userInteractive) { + this.userInteractive = userInteractive; + CurrentNumber.MinValue = 0; CurrentNumber.MaxValue = 1; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs index 6a2f2e4569..ca94078401 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels public DownloadProgressBar(BeatmapSetInfo beatmapSet) : base(beatmapSet) { - AddInternal(progressBar = new InteractionDisabledProgressBar + AddInternal(progressBar = new ProgressBar(false) { Height = 0, Alpha = 0, @@ -64,11 +64,5 @@ namespace osu.Game.Overlays.BeatmapListing.Panels } }, true); } - - private class InteractionDisabledProgressBar : ProgressBar - { - public override bool HandlePositionalInput => false; - public override bool HandleNonPositionalInput => false; - } } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 9beb859f28..d64c61044d 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -411,6 +411,11 @@ namespace osu.Game.Overlays private class HoverableProgressBar : ProgressBar { + public HoverableProgressBar() + : base(true) + { + } + protected override bool OnHover(HoverEvent e) { this.ResizeHeightTo(progress_height, 500, Easing.OutQuint); From 6deb10e0750423c60175975edf62e383535dc346 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 11:09:45 +0300 Subject: [PATCH 008/101] Add UI state display for each client's beatmap availability --- .../Participants/ParticipantPanel.cs | 2 +- .../Multiplayer/Participants/StateDisplay.cs | 193 +++++++++++------- 2 files changed, 119 insertions(+), 76 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f99655e305..17bf3a58ec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - userStateDisplay.Status = User.State; + userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index 8d2879fc93..4245628a59 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -8,119 +9,161 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { public class StateDisplay : CompositeDrawable { + private const double fade_time = 50; + + private SpriteIcon icon; + private OsuSpriteText text; + private ProgressBar progressBar; + public StateDisplay() { AutoSizeAxes = Axes.Both; Alpha = 0; } - private MultiplayerUserState status; - - private OsuSpriteText text; - private SpriteIcon icon; - - private const double fade_time = 50; - - public MultiplayerUserState Status - { - set - { - if (value == status) - return; - - status = value; - - if (IsLoaded) - updateStatus(); - } - } - [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { + this.colours = colours; + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Spacing = new Vector2(5), Children = new Drawable[] { - text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 12), - Colour = Color4Extensions.FromHex("#DDFFFF") - }, icon = new SpriteIcon { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Icon = FontAwesome.Solid.CheckCircle, Size = new Vector2(12), - } + }, + new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Children = new Drawable[] + { + progressBar = new ProgressBar(false) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BackgroundColour = Color4.Black.Opacity(0.4f), + FillColour = colours.Blue, + Alpha = 0f, + }, + text = new OsuSpriteText + { + Padding = new MarginPadding { Horizontal = 5f, Vertical = 1f }, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 12), + Colour = Color4Extensions.FromHex("#DDFFFF") + }, + } + }, } }; } - protected override void LoadComplete() - { - base.LoadComplete(); - updateStatus(); - } + private OsuColour colours; - [Resolved] - private OsuColour colours { get; set; } - - private void updateStatus() + public void UpdateStatus(MultiplayerUserState state, BeatmapAvailability availability) { - switch (status) + if (availability.State != DownloadState.LocallyAvailable) { - default: - this.FadeOut(fade_time); - return; + switch (availability.State) + { + case DownloadState.NotDownloaded: + progressBar.FadeOut(fade_time); + text.Text = "no map"; + icon.Icon = FontAwesome.Solid.MinusCircle; + icon.Colour = colours.RedLight; + break; - case MultiplayerUserState.Ready: - text.Text = "ready"; - icon.Icon = FontAwesome.Solid.CheckCircle; - icon.Colour = Color4Extensions.FromHex("#AADD00"); - break; + case DownloadState.Downloading: + Debug.Assert(availability.DownloadProgress != null); - case MultiplayerUserState.WaitingForLoad: - text.Text = "loading"; - icon.Icon = FontAwesome.Solid.PauseCircle; - icon.Colour = colours.Yellow; - break; + var progress = availability.DownloadProgress.Value; + progressBar.FadeIn(fade_time); + progressBar.CurrentTime = progress; - case MultiplayerUserState.Loaded: - text.Text = "loaded"; - icon.Icon = FontAwesome.Solid.DotCircle; - icon.Colour = colours.YellowLight; - break; + text.Text = "downloading map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Blue; + break; - case MultiplayerUserState.Playing: - text.Text = "playing"; - icon.Icon = FontAwesome.Solid.PlayCircle; - icon.Colour = colours.BlueLight; - break; + case DownloadState.Importing: + progressBar.FadeOut(fade_time); + text.Text = "importing map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Yellow; + break; + } + } + else + { + progressBar.FadeOut(fade_time); - case MultiplayerUserState.FinishedPlay: - text.Text = "results pending"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; + switch (state) + { + default: + this.FadeOut(fade_time); + return; - case MultiplayerUserState.Results: - text.Text = "results"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; + case MultiplayerUserState.Ready: + text.Text = "ready"; + icon.Icon = FontAwesome.Solid.CheckCircle; + icon.Colour = Color4Extensions.FromHex("#AADD00"); + break; + + case MultiplayerUserState.WaitingForLoad: + text.Text = "loading"; + icon.Icon = FontAwesome.Solid.PauseCircle; + icon.Colour = colours.Yellow; + break; + + case MultiplayerUserState.Loaded: + text.Text = "loaded"; + icon.Icon = FontAwesome.Solid.DotCircle; + icon.Colour = colours.YellowLight; + break; + + case MultiplayerUserState.Playing: + text.Text = "playing"; + icon.Icon = FontAwesome.Solid.PlayCircle; + icon.Colour = colours.BlueLight; + break; + + case MultiplayerUserState.FinishedPlay: + text.Text = "results pending"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + + case MultiplayerUserState.Results: + text.Text = "results"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + } } this.FadeIn(fade_time); From 6e34ab5d152f37d19702ff305c2a53323e89ef75 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Jan 2021 11:13:38 +0300 Subject: [PATCH 009/101] Rename WebOverlay to OnlineOverlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/DashboardOverlay.cs | 2 +- osu.Game/Overlays/NewsOverlay.cs | 2 +- osu.Game/Overlays/{WebOverlay.cs => OnlineOverlay.cs} | 4 ++-- osu.Game/Overlays/RankingsOverlay.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Overlays/{WebOverlay.cs => OnlineOverlay.cs} (91%) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ae1667d403..eafb7e95d5 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -23,7 +23,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapListingOverlay : WebOverlay + public class BeatmapListingOverlay : OnlineOverlay { [Resolved] private PreviewTrackManager previewTrackManager { get; set; } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 10953415ed..872621801a 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : WebOverlay // we don't provide a standard header for now. + public class BeatmapSetOverlay : OnlineOverlay // we don't provide a standard header for now. { public const float X_PADDING = 40; public const float Y_PADDING = 25; diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 5d99887053..5200b567ff 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class ChangelogOverlay : WebOverlay + public class ChangelogOverlay : OnlineOverlay { public readonly Bindable Current = new Bindable(); diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 3722d36388..39a23fe3d4 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : WebOverlay + public class DashboardOverlay : OnlineOverlay { private CancellationTokenSource cancellationToken; diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 268d2bb6a2..08e8331dd3 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -9,7 +9,7 @@ using osu.Game.Overlays.News.Displays; namespace osu.Game.Overlays { - public class NewsOverlay : WebOverlay + public class NewsOverlay : OnlineOverlay { private readonly Bindable article = new Bindable(null); diff --git a/osu.Game/Overlays/WebOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs similarity index 91% rename from osu.Game/Overlays/WebOverlay.cs rename to osu.Game/Overlays/OnlineOverlay.cs index aca767e32f..b44ccc32c9 100644 --- a/osu.Game/Overlays/WebOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays { - public abstract class WebOverlay : FullscreenOverlay + public abstract class OnlineOverlay : FullscreenOverlay where T : OverlayHeader { protected override Container Content => content; @@ -16,7 +16,7 @@ namespace osu.Game.Overlays protected readonly LoadingLayer Loading; private readonly Container content; - protected WebOverlay(OverlayColourScheme colourScheme) + protected OnlineOverlay(OverlayColourScheme colourScheme) : base(colourScheme) { FillFlowContainer flow = new FillFlowContainer diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 89853e9044..f6bbac4407 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : WebOverlay + public class RankingsOverlay : OnlineOverlay { protected Bindable Country => Header.Country; From 5f2e9c5485d854c3a5a85dee64613226fd77cd38 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 11:10:38 +0300 Subject: [PATCH 010/101] Add visual test case for displaying beatmap availability states --- .../TestSceneMultiplayerParticipantsList.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 968a869532..e2f1a13593 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -7,7 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Online; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Users; using osuTK; @@ -73,6 +75,20 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.User == secondUser); } + [Test] + public void TestBeatmapDownloadingStates() + { + AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); + AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddRepeatStep("increment progress", () => + { + var progress = this.ChildrenOfType().Single().User.BeatmapAvailability.DownloadProgress ?? 0; + Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f))); + }, 25); + AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); + AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); + } + [Test] public void TestToggleReadyState() { @@ -120,6 +136,26 @@ namespace osu.Game.Tests.Visual.Multiplayer }); Client.ChangeUserState(i, (MultiplayerUserState)RNG.Next(0, (int)MultiplayerUserState.Results + 1)); + + if (RNG.NextBool()) + { + var beatmapState = (DownloadState)RNG.Next(0, (int)DownloadState.LocallyAvailable + 1); + + switch (beatmapState) + { + case DownloadState.NotDownloaded: + Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.NotDownloaded()); + break; + + case DownloadState.Downloading: + Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Downloading(RNG.NextSingle())); + break; + + case DownloadState.Importing: + Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Importing()); + break; + } + } } }); } From e6ceaad73233c8322f93554ebcbf136da5c47285 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 17:23:51 +0300 Subject: [PATCH 011/101] Revert user state back to idle upon availability change --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 94288673fb..826859c598 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); - BeatmapAvailability.BindValueChanged(updateClientAvailability, true); + BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; @@ -210,10 +210,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault(); - private void updateClientAvailability(ValueChangedEvent _ = null) + private void updateBeatmapAvailability(ValueChangedEvent _ = null) { - if (client.Room != null) - client.ChangeBeatmapAvailability(BeatmapAvailability.Value).CatchUnobservedExceptions(true); + if (client.Room == null) + return; + + client.ChangeBeatmapAvailability(BeatmapAvailability.Value).CatchUnobservedExceptions(true); + + if (client.LocalUser?.State == MultiplayerUserState.Ready) + client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true); } private void onRoomUpdated() @@ -222,7 +227,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; if (client.LocalUser?.BeatmapAvailability.Equals(BeatmapAvailability.Value) == false) - updateClientAvailability(); + updateBeatmapAvailability(); } private void onReadyClick() From c6b0f3c247aa54a64d9e61c47643a09ea91ec177 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Jan 2021 21:22:50 +0300 Subject: [PATCH 012/101] Implement TabbableOnlineOverlay component --- osu.Game/Overlays/DashboardOverlay.cs | 92 ++----------------- osu.Game/Overlays/RankingsOverlay.cs | 46 ++-------- osu.Game/Overlays/TabbableOnlineOverlay.cs | 101 +++++++++++++++++++++ 3 files changed, 116 insertions(+), 123 deletions(-) create mode 100644 osu.Game/Overlays/TabbableOnlineOverlay.cs diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 39a23fe3d4..83ad8faf1c 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -2,115 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Overlays.Dashboard; using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : OnlineOverlay + public class DashboardOverlay : TabbableOnlineOverlay { - private CancellationTokenSource cancellationToken; - public DashboardOverlay() : base(OverlayColourScheme.Purple) { } - private readonly IBindable apiState = new Bindable(); - - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - apiState.BindTo(api.State); - apiState.BindValueChanged(onlineStateChanged, true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Header.Current.BindValueChanged(onTabChanged); - } - protected override DashboardOverlayHeader CreateHeader() => new DashboardOverlayHeader(); - private bool displayUpdateRequired = true; - - protected override void PopIn() + protected override void CreateDisplayToLoad(DashboardOverlayTabs tab) { - base.PopIn(); - - // We don't want to create a new display on every call, only when exiting from fully closed state. - if (displayUpdateRequired) - { - Header.Current.TriggerChange(); - displayUpdateRequired = false; - } - } - - protected override void PopOutComplete() - { - base.PopOutComplete(); - loadDisplay(Empty()); - displayUpdateRequired = true; - } - - private void loadDisplay(Drawable display) - { - ScrollFlow.ScrollToStart(); - - LoadComponentAsync(display, loaded => - { - if (API.IsLoggedIn) - Loading.Hide(); - - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); - } - - private void onTabChanged(ValueChangedEvent tab) - { - cancellationToken?.Cancel(); - Loading.Show(); - - if (!API.IsLoggedIn) - { - loadDisplay(Empty()); - return; - } - - switch (tab.NewValue) + switch (tab) { case DashboardOverlayTabs.Friends: - loadDisplay(new FriendDisplay()); + LoadDisplay(new FriendDisplay()); break; case DashboardOverlayTabs.CurrentlyPlaying: - loadDisplay(new CurrentlyPlayingDisplay()); + LoadDisplay(new CurrentlyPlayingDisplay()); break; default: - throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented"); + throw new NotImplementedException($"Display for {tab} tab is not implemented"); } } - - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => - { - if (State.Value == Visibility.Hidden) - return; - - Header.Current.TriggerChange(); - }); - - protected override void Dispose(bool isDisposing) - { - cancellationToken?.Cancel(); - base.Dispose(isDisposing); - } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index f6bbac4407..6cd72d6e2c 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -8,20 +8,18 @@ using osu.Game.Overlays.Rankings; using osu.Game.Users; using osu.Game.Rulesets; using osu.Game.Online.API; -using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : OnlineOverlay + public class RankingsOverlay : TabbableOnlineOverlay { protected Bindable Country => Header.Country; protected Bindable Scope => Header.Current; private APIRequest lastRequest; - private CancellationTokenSource cancellationToken; [Resolved] private IAPIProvider api { get; set; } @@ -34,11 +32,6 @@ namespace osu.Game.Overlays { } - [BackgroundDependencyLoader] - private void load() - { - } - protected override void LoadComplete() { base.LoadComplete(); @@ -54,6 +47,8 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); }); + // Unbind events from scope so base class event will not be called + Scope.UnbindEvents(); Scope.BindValueChanged(_ => { // country filtering is only valid for performance scope. @@ -70,8 +65,6 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); }); - - Scheduler.AddOnce(loadNewContent); } protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); @@ -92,16 +85,13 @@ namespace osu.Game.Overlays Show(); } - private void loadNewContent() + protected override void CreateDisplayToLoad(RankingsScope tab) { - Loading.Show(); - - cancellationToken?.Cancel(); lastRequest?.Cancel(); if (Scope.Value == RankingsScope.Spotlights) { - loadContent(new SpotlightsLayout + LoadDisplay(new SpotlightsLayout { Ruleset = { BindTarget = ruleset } }); @@ -113,12 +103,12 @@ namespace osu.Game.Overlays if (request == null) { - loadContent(null); + LoadDisplay(Empty()); return; } - request.Success += () => Schedule(() => loadContent(createTableFromResponse(request))); - request.Failure += _ => Schedule(() => loadContent(null)); + request.Success += () => Schedule(() => LoadDisplay(createTableFromResponse(request))); + request.Failure += _ => Schedule(() => LoadDisplay(Empty())); api.Queue(request); } @@ -163,29 +153,11 @@ namespace osu.Game.Overlays return null; } - private void loadContent(Drawable content) - { - ScrollFlow.ScrollToStart(); - - if (content == null) - { - Clear(); - Loading.Hide(); - return; - } - - LoadComponentAsync(content, loaded => - { - Loading.Hide(); - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); - } + private void loadNewContent() => OnTabChanged(Scope.Value); protected override void Dispose(bool isDisposing) { lastRequest?.Cancel(); - cancellationToken?.Cancel(); - base.Dispose(isDisposing); } } diff --git a/osu.Game/Overlays/TabbableOnlineOverlay.cs b/osu.Game/Overlays/TabbableOnlineOverlay.cs new file mode 100644 index 0000000000..cbcf3cd96e --- /dev/null +++ b/osu.Game/Overlays/TabbableOnlineOverlay.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; + +namespace osu.Game.Overlays +{ + public abstract class TabbableOnlineOverlay : OnlineOverlay + where THeader : TabControlOverlayHeader + { + private readonly IBindable apiState = new Bindable(); + + private CancellationTokenSource cancellationToken; + private bool displayUpdateRequired = true; + + protected TabbableOnlineOverlay(OverlayColourScheme colourScheme) + : base(colourScheme) + { + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Header.Current.BindValueChanged(tab => OnTabChanged(tab.NewValue)); + } + + protected override void PopIn() + { + base.PopIn(); + + // We don't want to create a new display on every call, only when exiting from fully closed state. + if (displayUpdateRequired) + { + Header.Current.TriggerChange(); + displayUpdateRequired = false; + } + } + + protected override void PopOutComplete() + { + base.PopOutComplete(); + LoadDisplay(Empty()); + displayUpdateRequired = true; + } + + protected void LoadDisplay(Drawable display) + { + ScrollFlow.ScrollToStart(); + + LoadComponentAsync(display, loaded => + { + if (API.IsLoggedIn) + Loading.Hide(); + + Child = loaded; + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + protected void OnTabChanged(TEnum tab) + { + cancellationToken?.Cancel(); + Loading.Show(); + + if (!API.IsLoggedIn) + { + LoadDisplay(Empty()); + return; + } + + CreateDisplayToLoad(tab); + } + + protected abstract void CreateDisplayToLoad(TEnum tab); + + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => + { + if (State.Value == Visibility.Hidden) + return; + + Header.Current.TriggerChange(); + }); + + protected override void Dispose(bool isDisposing) + { + cancellationToken?.Cancel(); + base.Dispose(isDisposing); + } + } +} From 65ece1aa722b74e6590b4b3d208a10141238164d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Jan 2021 07:50:41 +0300 Subject: [PATCH 013/101] Mark OverlayHeader as NotNull in FullscreenOverlay --- osu.Game/Overlays/FullscreenOverlay.cs | 8 ++++--- osu.Game/Overlays/OnlineOverlay.cs | 32 ++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index d65213f573..d0a0c994aa 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -16,9 +17,9 @@ namespace osu.Game.Overlays public abstract class FullscreenOverlay : WaveOverlayContainer, INamedOverlayComponent where T : OverlayHeader { - public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty; - public virtual string Title => Header?.Title.Title ?? string.Empty; - public virtual string Description => Header?.Title.Description ?? string.Empty; + public virtual string IconTexture => Header.Title.IconTexture ?? string.Empty; + public virtual string Title => Header.Title.Title ?? string.Empty; + public virtual string Description => Header.Title.Description ?? string.Empty; public T Header { get; } @@ -76,6 +77,7 @@ namespace osu.Game.Overlays Waves.FourthWaveColour = ColourProvider.Dark3; } + [NotNull] protected abstract T CreateHeader(); protected virtual Color4 GetBackgroundColour() => ColourProvider.Background5; diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index b44ccc32c9..4a7318d065 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -19,29 +19,27 @@ namespace osu.Game.Overlays protected OnlineOverlay(OverlayColourScheme colourScheme) : base(colourScheme) { - FillFlowContainer flow = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical - }; - - if (Header != null) - flow.Add(Header.With(h => h.Depth = -float.MaxValue)); - - flow.Add(content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }); - base.Content.AddRange(new Drawable[] { ScrollFlow = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, - Child = flow + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + Header.With(h => h.Depth = -float.MaxValue), + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } }, Loading = new LoadingLayer(true) }); From 623b47f9af503a400facfea44e2db230db42a279 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 21:25:05 +0900 Subject: [PATCH 014/101] Add flag to toggle follow circle tracking for slider heads --- .../Objects/Drawables/DrawableSliderHead.cs | 14 ++++++++++---- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 4 ++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index acc95ab036..c051a9918d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderHead : DrawableHitCircle { + public new SliderHeadCircle HitObject => (SliderHeadCircle)base.HitObject; + [CanBeNull] public Slider Slider => DrawableSlider?.HitObject; @@ -59,12 +61,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.Update(); Debug.Assert(Slider != null); + Debug.Assert(HitObject != null); - double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); + if (HitObject.TrackFollowCircle) + { + double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); - //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. - if (!IsHit) - Position = Slider.CurvePositionAt(completionProgress); + //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. + if (!IsHit) + Position = Slider.CurvePositionAt(completionProgress); + } } public Action OnShake; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index f6d46aeef5..5fc480883a 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -5,5 +5,9 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderHeadCircle : HitCircle { + /// + /// Makes the head circle track the follow circle when the start time is reached. + /// + public bool TrackFollowCircle = true; } } From 03b7817887ea059719094f001a81c5b4f1c9ee36 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 22:12:20 +0900 Subject: [PATCH 015/101] Add flags to return to classic slider scoring --- .../Objects/Drawables/DrawableHitCircle.cs | 9 ++++++- .../Objects/Drawables/DrawableSlider.cs | 24 ++++++++++++++++++- .../Objects/Drawables/DrawableSliderHead.cs | 15 ++++++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++++- .../Objects/SliderHeadCircle.cs | 14 ++++++++++- 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3c0260f5f5..77094f928b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; } - var result = HitObject.HitWindows.ResultFor(timeOffset); + var result = ResultFor(timeOffset); if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false) { @@ -146,6 +146,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } + /// + /// Retrieves the for a time offset. + /// + /// The time offset. + /// The hit result, or if doesn't result in a judgement. + protected virtual HitResult ResultFor(double timeOffset) => HitObject.HitWindows.ResultFor(timeOffset); + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 511cbc2347..7061ce59d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -249,7 +250,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < HitObject.EndTime) return; - ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + if (HitObject.IgnoreJudgement) + { + ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + return; + } + + // If not ignoring judgement, score proportionally based on the number of ticks hit, counting the head circle as a tick. + ApplyResult(r => + { + int totalTicks = NestedHitObjects.Count; + int hitTicks = NestedHitObjects.Count(h => h.IsHit); + double hitFraction = (double)totalTicks / hitTicks; + + if (hitTicks == totalTicks) + r.Type = HitResult.Great; + else if (hitFraction >= 0.5) + r.Type = HitResult.Ok; + else if (hitFraction > 0) + r.Type = HitResult.Meh; + else + r.Type = HitResult.Miss; + }); } public override void PlaySamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index c051a9918d..08e9c5eb14 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; + private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; @@ -73,6 +76,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override HitResult ResultFor(double timeOffset) + { + Debug.Assert(HitObject != null); + + if (HitObject.JudgeAsNormalHitCircle) + return base.ResultFor(timeOffset); + + // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. + var result = base.ResultFor(timeOffset); + return result.IsHit() ? HitResult.IgnoreHit : result; + } + public Action OnShake; public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1670df24a8..e3365a8ccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -114,6 +114,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double TickDistanceMultiplier = 1; + /// + /// Whether this 's judgement should be ignored. + /// If false, this will be judged proportionally to the number of ticks hit. + /// + public bool IgnoreJudgement = true; + [JsonIgnore] public HitCircle HeadCircle { get; protected set; } @@ -233,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects HeadCircle.Samples = this.GetNodeSamples(0); } - public override Judgement CreateJudgement() => new OsuIgnoreJudgement(); + public override Judgement CreateJudgement() => IgnoreJudgement ? new OsuIgnoreJudgement() : new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 5fc480883a..13eac60300 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -1,13 +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 osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; + namespace osu.Game.Rulesets.Osu.Objects { public class SliderHeadCircle : HitCircle { /// - /// Makes the head circle track the follow circle when the start time is reached. + /// Makes this track the follow circle when the start time is reached. + /// If false, this will be pinned to its initial position in the slider. /// public bool TrackFollowCircle = true; + + /// + /// Whether to treat this as a normal for judgement purposes. + /// If false, judgement will be ignored. + /// + public bool JudgeAsNormalHitCircle = true; + + public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new OsuIgnoreJudgement(); } } From 2f22dbe06be3645ed9c5e8f99cc015414f578256 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 22:42:50 +0900 Subject: [PATCH 016/101] Make sliders display judgements when not ignored --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 13f5960bd4..79655c33e4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject?.HitObject is OsuHitObject osuObject) { - Position = osuObject.StackedPosition; + Position = osuObject.StackedEndPosition; Scale = new Vector2(osuObject.Scale); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 7061ce59d0..e607163b3e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SliderBall Ball { get; private set; } public SkinnableDrawable Body { get; private set; } - public override bool DisplayResult => false; + public override bool DisplayResult => !HitObject.IgnoreJudgement; private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; From 3b5c67a0630681b6e1e2f0d52486e241772661d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 23:08:59 +0900 Subject: [PATCH 017/101] Add OsuModClassic --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 43 +++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 2 files changed, 44 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs new file mode 100644 index 0000000000..5542580979 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModClassic : Mod, IApplicableToHitObject + { + public override string Name => "Classic"; + + public override string Acronym => "CL"; + + public override double ScoreMultiplier => 1; + + public override IconUsage? Icon => FontAwesome.Solid.History; + + public override string Description => "Feeling nostalgic?"; + + public override bool Ranked => false; + + public void ApplyToHitObject(HitObject hitObject) + { + switch (hitObject) + { + case Slider slider: + slider.IgnoreJudgement = false; + + foreach (var head in slider.NestedHitObjects.OfType()) + { + head.TrackFollowCircle = false; + head.JudgeAsNormalHitCircle = false; + } + + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index cba0c5be14..18324a18a8 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -163,6 +163,7 @@ namespace osu.Game.Rulesets.Osu { new OsuModTarget(), new OsuModDifficultyAdjust(), + new OsuModClassic() }; case ModType.Automation: From 4bc324f040b3bfaca1e57af88f00558a6237d6bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:29:32 +0900 Subject: [PATCH 018/101] Rename parameter to make more sense --- osu.Game/Graphics/UserInterface/ProgressBar.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index 4ee1c73bf5..50367e600e 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -40,13 +40,18 @@ namespace osu.Game.Graphics.UserInterface set => CurrentNumber.Value = value; } - private readonly bool userInteractive; - public override bool HandlePositionalInput => userInteractive; - public override bool HandleNonPositionalInput => userInteractive; + private readonly bool allowSeek; - public ProgressBar(bool userInteractive) + public override bool HandlePositionalInput => allowSeek; + public override bool HandleNonPositionalInput => allowSeek; + + /// + /// Construct a new progress bar. + /// + /// Whether the user should be allowed to click/drag to adjust the value. + public ProgressBar(bool allowSeek) { - this.userInteractive = userInteractive; + this.allowSeek = allowSeek; CurrentNumber.MinValue = 0; CurrentNumber.MaxValue = 1; From d1f9aa52a4c528053d7ee45344ad5ec15dff4fe5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:33:48 +0900 Subject: [PATCH 019/101] Inline variable --- .../OnlinePlay/Multiplayer/Participants/StateDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index 4245628a59..c117c026dc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -101,9 +101,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants case DownloadState.Downloading: Debug.Assert(availability.DownloadProgress != null); - var progress = availability.DownloadProgress.Value; progressBar.FadeIn(fade_time); - progressBar.CurrentTime = progress; + progressBar.CurrentTime = availability.DownloadProgress.Value; text.Text = "downloading map"; icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; From a4551dc1eeb2eb6afb9dec61752559eaa6e5632c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:31:22 +0900 Subject: [PATCH 020/101] Add object-ordered hit policy --- .../UI/ObjectOrderedHitPolicy.cs | 55 +++++++++++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs new file mode 100644 index 0000000000..fdab241a9b --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -0,0 +1,55 @@ +// 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 System.Linq; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.UI +{ + /// + /// Ensures that s are hit in order of appearance. The classic note lock. + /// + /// Hits will be blocked until the previous s have been judged. + /// + /// + public class ObjectOrderedHitPolicy : IHitPolicy + { + private IEnumerable hitObjects; + + public void SetHitObjects(IEnumerable hitObjects) => this.hitObjects = hitObjects; + + 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) + { + foreach (var obj in hitObjects) + { + if (obj.HitObject.StartTime >= targetTime) + yield break; + + switch (obj) + { + case DrawableSpinner _: + continue; + + case DrawableSlider slider: + yield return slider.HeadCircle; + + break; + + default: + yield return obj; + + break; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c7900558a0..6cb890323b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; private readonly FollowPointRenderer followPoints; - private readonly StartTimeOrderedHitPolicy hitPolicy; + private readonly IHitPolicy hitPolicy; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - hitPolicy = new StartTimeOrderedHitPolicy(); + hitPolicy = new ObjectOrderedHitPolicy(); hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); var hitWindows = new OsuHitWindows(); From 6aece18f8dedc392a84996d1c7e4a906b72b827e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 15:23:03 +0900 Subject: [PATCH 021/101] Add OOHP tests --- .../TestSceneObjectOrderedHitPolicy.cs | 491 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 29 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 17 +- 3 files changed, 529 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs new file mode 100644 index 0000000000..039a4f142f --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -0,0 +1,491 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Screens; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +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.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneObjectOrderedHitPolicy : RateAdjustedBeatmapTestScene + { + private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss + private const double late_miss_window = 500; // time after +500 is considered a miss + + /// + /// Tests clicking a future circle before the first circle's start time, while the first circle HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTime() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Miss); + addJudgementOffsetAssert(hitObjects[0], late_miss_window); + } + + /// + /// Tests clicking a future circle at the first circle's start time, while the first circle HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAtFirstCircleTime() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Miss); + addJudgementOffsetAssert(hitObjects[0], late_miss_window); + } + + /// + /// Tests clicking a future circle after the first circle's start time, while the first circle HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAfterFirstCircleTime() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle + 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Miss); + addJudgementOffsetAssert(hitObjects[0], late_miss_window); + } + + /// + /// Tests clicking a future circle before the first circle's start time, while the first circle HAS been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 + addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 + } + + /// + /// Tests clicking a future circle after the first circle's start time, while the first circle HAS been judged. + /// + [Test] + public void TestClickSecondCircleAfterFirstCircleTimeWithFirstCircleJudged() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 + addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time + } + + /// + /// Tests clicking a future circle after a slider's start time, but hitting all slider ticks. + /// + [Test] + public void TestMissSliderHeadAndHitAllSliderTicks() + { + const double time_slider = 1500; + const double time_circle = 1510; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_slider, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); + } + + /// + /// Tests clicking hitting future slider ticks before a circle. + /// + [Test] + public void TestHitSliderTicksBeforeCircle() + { + const double time_slider = 1500; + const double time_circle = 1510; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(30); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle + late_miss_window - 100, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_circle + late_miss_window - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); + } + + /// + /// Tests clicking a future circle before a spinner. + /// + [Test] + public void TestHitCircleBeforeSpinner() + { + const double time_spinner = 1500; + const double time_circle = 1800; + Vector2 positionCircle = Vector2.Zero; + + var hitObjects = new List + { + new TestSpinner + { + StartTime = time_spinner, + Position = new Vector2(256, 192), + EndTime = time_spinner + 1000, + }, + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_spinner - 100, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 40, Position = new Vector2(256, 212), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 50, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + } + + [Test] + public void TestHitSliderHeadBeforeHitCircle() + { + const double time_circle = 1000; + const double time_slider = 1200; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_circle - 100, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + } + + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) + { + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); + } + + private void addJudgementAssert(string name, Func hitObject, HitResult result) + { + AddAssert($"{name} judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); + } + + private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) + { + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", + () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); + } + + private ScoreAccessibleReplayPlayer currentPlayer; + private List judgementResults; + + private void performTest(List hitObjects, List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = hitObjects, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Ruleset = new OsuRuleset().RulesetInfo + }, + }); + + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + + SelectedMods.Value = new[] { new OsuModClassic() }; + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + } + + private class TestHitCircle : HitCircle + { + protected override HitWindows CreateHitWindows() => new TestHitWindows(); + } + + private class TestSlider : Slider + { + public TestSlider() + { + DefaultsApplied += _ => + { + HeadCircle.HitWindows = new TestHitWindows(); + TailCircle.HitWindows = new TestHitWindows(); + + HeadCircle.HitWindows.SetDifficulty(0); + TailCircle.HitWindows.SetDifficulty(0); + }; + } + } + + private class TestSpinner : Spinner + { + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + SpinsRequired = 1; + } + } + + private class TestHitWindows : HitWindows + { + private static readonly DifficultyRange[] ranges = + { + new DifficultyRange(HitResult.Great, 500, 500, 500), + new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window), + }; + + public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; + + protected override DifficultyRange[] GetRanges() => ranges; + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 5542580979..6f41bcc0b0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -2,14 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : Mod, IApplicableToHitObject + public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableRuleset { public override string Name => "Classic"; @@ -23,21 +27,38 @@ namespace osu.Game.Rulesets.Osu.Mods public override bool Ranked => false; + [SettingSource("Disable slider head judgement", "Scores sliders proportionally to the number of ticks hit.")] + public Bindable DisableSliderHeadJudgement { get; } = new BindableBool(true); + + [SettingSource("Disable slider head tracking", "Pins slider heads at their starting position, regardless of time.")] + public Bindable DisableSliderHeadTracking { get; } = new BindableBool(true); + + [SettingSource("Disable note lock lenience", "Applies note lock to the full hit window.")] + public Bindable DisableLenientNoteLock { get; } = new BindableBool(true); + public void ApplyToHitObject(HitObject hitObject) { switch (hitObject) { case Slider slider: - slider.IgnoreJudgement = false; + slider.IgnoreJudgement = !DisableSliderHeadJudgement.Value; foreach (var head in slider.NestedHitObjects.OfType()) { - head.TrackFollowCircle = false; - head.JudgeAsNormalHitCircle = false; + head.TrackFollowCircle = !DisableSliderHeadTracking.Value; + head.JudgeAsNormalHitCircle = !DisableSliderHeadJudgement.Value; } break; } } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + var osuRuleset = (DrawableOsuRuleset)drawableRuleset; + + if (!DisableLenientNoteLock.Value) + osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); + } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 6cb890323b..9bd1dc74b7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; private readonly FollowPointRenderer followPoints; - private readonly IHitPolicy hitPolicy; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -54,11 +53,9 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - hitPolicy = new ObjectOrderedHitPolicy(); - hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); + HitPolicy = new ObjectOrderedHitPolicy(); var hitWindows = new OsuHitWindows(); - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgmentLoaded)); @@ -67,6 +64,18 @@ namespace osu.Game.Rulesets.Osu.UI NewResult += onNewResult; } + private IHitPolicy hitPolicy; + + public IHitPolicy HitPolicy + { + get => hitPolicy; + set + { + hitPolicy = value ?? throw new ArgumentNullException(nameof(value)); + hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); + } + } + protected override void OnNewDrawableHitObject(DrawableHitObject drawable) { ((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable; From 1b6a05279847569be9d5568bc9812fa55b7fe503 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:46:03 +0900 Subject: [PATCH 022/101] Refactor logic to suck a bit less --- .../Multiplayer/Participants/StateDisplay.cs | 161 +++++++++--------- 1 file changed, 83 insertions(+), 78 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index c117c026dc..c571b51c83 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -87,85 +88,89 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants public void UpdateStatus(MultiplayerUserState state, BeatmapAvailability availability) { - if (availability.State != DownloadState.LocallyAvailable) - { - switch (availability.State) - { - case DownloadState.NotDownloaded: - progressBar.FadeOut(fade_time); - text.Text = "no map"; - icon.Icon = FontAwesome.Solid.MinusCircle; - icon.Colour = colours.RedLight; - break; - - case DownloadState.Downloading: - Debug.Assert(availability.DownloadProgress != null); - - progressBar.FadeIn(fade_time); - progressBar.CurrentTime = availability.DownloadProgress.Value; - - text.Text = "downloading map"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; - icon.Colour = colours.Blue; - break; - - case DownloadState.Importing: - progressBar.FadeOut(fade_time); - text.Text = "importing map"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; - icon.Colour = colours.Yellow; - break; - } - } - else - { - progressBar.FadeOut(fade_time); - - switch (state) - { - default: - this.FadeOut(fade_time); - return; - - case MultiplayerUserState.Ready: - text.Text = "ready"; - icon.Icon = FontAwesome.Solid.CheckCircle; - icon.Colour = Color4Extensions.FromHex("#AADD00"); - break; - - case MultiplayerUserState.WaitingForLoad: - text.Text = "loading"; - icon.Icon = FontAwesome.Solid.PauseCircle; - icon.Colour = colours.Yellow; - break; - - case MultiplayerUserState.Loaded: - text.Text = "loaded"; - icon.Icon = FontAwesome.Solid.DotCircle; - icon.Colour = colours.YellowLight; - break; - - case MultiplayerUserState.Playing: - text.Text = "playing"; - icon.Icon = FontAwesome.Solid.PlayCircle; - icon.Colour = colours.BlueLight; - break; - - case MultiplayerUserState.FinishedPlay: - text.Text = "results pending"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; - - case MultiplayerUserState.Results: - text.Text = "results"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; - } - } - + // the only case where the progress bar is used does its own local fade in. + // starting by fading out is a sane default. + progressBar.FadeOut(fade_time); this.FadeIn(fade_time); + + switch (state) + { + case MultiplayerUserState.Idle: + showBeatmapAvailability(availability); + break; + + case MultiplayerUserState.Ready: + text.Text = "ready"; + icon.Icon = FontAwesome.Solid.CheckCircle; + icon.Colour = Color4Extensions.FromHex("#AADD00"); + break; + + case MultiplayerUserState.WaitingForLoad: + text.Text = "loading"; + icon.Icon = FontAwesome.Solid.PauseCircle; + icon.Colour = colours.Yellow; + break; + + case MultiplayerUserState.Loaded: + text.Text = "loaded"; + icon.Icon = FontAwesome.Solid.DotCircle; + icon.Colour = colours.YellowLight; + break; + + case MultiplayerUserState.Playing: + text.Text = "playing"; + icon.Icon = FontAwesome.Solid.PlayCircle; + icon.Colour = colours.BlueLight; + break; + + case MultiplayerUserState.FinishedPlay: + text.Text = "results pending"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + + case MultiplayerUserState.Results: + text.Text = "results"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(state), state, null); + } + } + + private void showBeatmapAvailability(BeatmapAvailability availability) + { + switch (availability.State) + { + default: + this.FadeOut(fade_time); + break; + + case DownloadState.NotDownloaded: + text.Text = "no map"; + icon.Icon = FontAwesome.Solid.MinusCircle; + icon.Colour = colours.RedLight; + break; + + case DownloadState.Downloading: + Debug.Assert(availability.DownloadProgress != null); + + progressBar.FadeIn(fade_time); + progressBar.CurrentTime = availability.DownloadProgress.Value; + + text.Text = "downloading map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Blue; + break; + + case DownloadState.Importing: + text.Text = "importing map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Yellow; + break; + } } } } From 98c4573240670b7992894d2ddaf84c8f9acdf476 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:52:49 +0900 Subject: [PATCH 023/101] Add assertions covering new test --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 4fec1c6dd8..b025440d04 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -82,12 +83,20 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + + AddUntilStep("progress bar visible", () => this.ChildrenOfType().Single().IsPresent); + AddRepeatStep("increment progress", () => { var progress = this.ChildrenOfType().Single().User.BeatmapAvailability.DownloadProgress ?? 0; Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f))); }, 25); + + AddAssert("progress bar increased", () => this.ChildrenOfType().Single().Current.Value > 0); + AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); + AddUntilStep("progress bar not visible", () => !this.ChildrenOfType().Single().IsPresent); + AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); } From 3aa3692ed4be0a5b94e4bbb9b489063b6532b0da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 15:56:13 +0900 Subject: [PATCH 024/101] Disable snaking out when tracking is disabled --- .../Sliders/SliderCircleSelectionBlueprint.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Skinning/Default/PlaySliderBody.cs | 22 ++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index a0392fe536..dec9cd8622 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.Update(); - CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle); + CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)HitObject.HeadCircle : HitObject.TailCircle); } // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e3365a8ccf..01694a838b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Objects public bool IgnoreJudgement = true; [JsonIgnore] - public HitCircle HeadCircle { get; protected set; } + public SliderHeadCircle HeadCircle { get; protected set; } [JsonIgnore] public SliderTailCircle TailCircle { get; protected set; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index e77c93c721..e9b4bb416c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } + private readonly Bindable snakingOut = new Bindable(); + [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) { @@ -35,11 +37,29 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default accentColour = drawableObject.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true); + SnakingOut.BindTo(snakingOut); config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); - config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; + + drawableObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(drawableObject); + } + + private void onHitObjectApplied(DrawableHitObject obj) + { + var drawableSlider = (DrawableSlider)obj; + if (drawableSlider.HitObject == null) + return; + + if (!drawableSlider.HitObject.HeadCircle.TrackFollowCircle) + { + // When not tracking the follow circle, force the path to not snake out as it looks better that way. + SnakingOut.UnbindFrom(snakingOut); + SnakingOut.Value = false; + } } private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour) From 1368d551529ac09ecb1d47c5d32c0ddbf5f313b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:58:27 +0900 Subject: [PATCH 025/101] Add test coverage of precedence of display --- .../TestSceneMultiplayerParticipantsList.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index b025440d04..c3852fafd4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -78,13 +78,27 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.User == secondUser); } + [Test] + public void TestGameStateHasPriorityOverDownloadState() + { + AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + checkProgressBarVisibility(true); + + AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Results)); + checkProgressBarVisibility(false); + AddUntilStep("ready mark visible", () => this.ChildrenOfType().Single().IsPresent); + + AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Idle)); + checkProgressBarVisibility(true); + } + [Test] public void TestBeatmapDownloadingStates() { AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); - AddUntilStep("progress bar visible", () => this.ChildrenOfType().Single().IsPresent); + checkProgressBarVisibility(true); AddRepeatStep("increment progress", () => { @@ -95,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("progress bar increased", () => this.ChildrenOfType().Single().Current.Value > 0); AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); - AddUntilStep("progress bar not visible", () => !this.ChildrenOfType().Single().IsPresent); + checkProgressBarVisibility(false); AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); } @@ -197,5 +211,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep($"set state: {state}", () => Client.ChangeUserState(0, state)); } } + + private void checkProgressBarVisibility(bool visible) => + AddUntilStep($"progress bar {(visible ? "is" : "is not")}visible", () => + this.ChildrenOfType().Single().IsPresent == visible); } } From 9ba5ae3db7c7f235fab1d44fd75a2e2a2f0ded94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:17:02 +0900 Subject: [PATCH 026/101] Remove lots of unnecessary client side logic --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 5bcb1d6dc8..bd1ec9c54a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -270,7 +270,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); - client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; isConnected = client.IsConnected.GetBoundCopy(); @@ -323,24 +322,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.ChangeUserMods(mods.NewValue); } - private void updateBeatmapAvailability(ValueChangedEvent _ = null) + private void updateBeatmapAvailability(ValueChangedEvent availability) { if (client.Room == null) return; - client.ChangeBeatmapAvailability(BeatmapAvailability.Value); - - if (client.LocalUser?.State == MultiplayerUserState.Ready) - client.ChangeState(MultiplayerUserState.Idle); - } - - private void onRoomUpdated() - { - if (client.Room == null) - return; - - if (client.LocalUser?.BeatmapAvailability.Equals(BeatmapAvailability.Value) == false) - updateBeatmapAvailability(); + client.ChangeBeatmapAvailability(availability.NewValue); } private void onReadyClick() @@ -392,10 +379,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.Dispose(isDisposing); if (client != null) - { client.LoadRequested -= onLoadRequested; - client.RoomUpdated -= onRoomUpdated; - } } private class UserModSelectOverlay : ModSelectOverlay From be91f54349202c6a633f0de9728ede538b120c9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:19:45 +0900 Subject: [PATCH 027/101] Add back edge case with comment --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index bd1ec9c54a..39e179262e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -328,6 +328,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; client.ChangeBeatmapAvailability(availability.NewValue); + + // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. + if (client.LocalUser?.State == MultiplayerUserState.Ready) + client.ChangeState(MultiplayerUserState.Idle); } private void onReadyClick() From ee3367d7c549acecb778c652365c290e3e186e5b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 16:59:13 +0900 Subject: [PATCH 028/101] Add classic slider ball tracking --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 17 ++++++++++++++++- .../Skinning/Default/SliderBall.cs | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 6f41bcc0b0..df3afb7063 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -1,19 +1,22 @@ // 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 System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableRuleset + public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset { public override string Name => "Classic"; @@ -36,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Disable note lock lenience", "Applies note lock to the full hit window.")] public Bindable DisableLenientNoteLock { get; } = new BindableBool(true); + [SettingSource("Disable exact slider follow circle tracking", "Makes the slider follow circle track its final size at all times.")] + public Bindable DisableExactFollowCircleTracking { get; } = new BindableBool(true); + public void ApplyToHitObject(HitObject hitObject) { switch (hitObject) @@ -60,5 +66,14 @@ namespace osu.Game.Rulesets.Osu.Mods if (!DisableLenientNoteLock.Value) osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var obj in drawables) + { + if (obj is DrawableSlider slider) + slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index a96beb66d4..da3debbd42 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -31,6 +31,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default set => ball.Colour = value; } + /// + /// Whether to track accurately to the visual size of this . + /// If false, tracking will be performed at the final scale at all times. + /// + public bool TrackVisualSize = true; + private readonly Drawable followCircle; private readonly DrawableSlider drawableSlider; private readonly Drawable ball; @@ -94,7 +100,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); + if (TrackVisualSize) + followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); + else + { + // We need to always be tracking the final size, at both endpoints. For now, this is achieved by removing the scale duration. + followCircle.ScaleTo(tracking ? 2.4f : 1f); + } + followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); } } From 791cbb7f03e9637a123b83b77f339c3d44abbf1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 17:17:29 +0900 Subject: [PATCH 029/101] Don't reset ready state if the map is locally available --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 39e179262e..53c939115c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -330,7 +330,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.ChangeBeatmapAvailability(availability.NewValue); // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. - if (client.LocalUser?.State == MultiplayerUserState.Ready) + if (availability.NewValue != Online.Rooms.BeatmapAvailability.LocallyAvailable() + && client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } From 110458612d730e449174fe533606faecc041785b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 17:19:23 +0900 Subject: [PATCH 030/101] Avoid handling null playlist items when updating avaialability display --- .../Rooms/OnlinePlayBeatmapAvailablilityTracker.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index ad4b3c5151..dcb366ddab 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -36,7 +36,15 @@ namespace osu.Game.Online.Rooms { base.LoadComplete(); - SelectedItem.BindValueChanged(item => Model.Value = item.NewValue?.Beatmap.Value.BeatmapSet, true); + SelectedItem.BindValueChanged(item => + { + // the underlying playlist is regularly cleared for maintenance purposes (things which probably need to be fixed eventually). + // to avoid exposing a state change when there may actually be none, ignore all nulls for now. + if (item.NewValue == null) + return; + + Model.Value = item.NewValue.Beatmap.Value.BeatmapSet; + }, true); } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) From a5855f5d28f8e7db6b9d4038806e5d70b38c3a7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 17:33:48 +0900 Subject: [PATCH 031/101] Move follow circle tracking to DrawableSliderHead --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 15 ++++++++++----- .../Objects/Drawables/DrawableSliderHead.cs | 8 +++++++- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 6 ------ .../Skinning/Default/PlaySliderBody.cs | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index df3afb7063..8cd6676f9d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -50,10 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods slider.IgnoreJudgement = !DisableSliderHeadJudgement.Value; foreach (var head in slider.NestedHitObjects.OfType()) - { - head.TrackFollowCircle = !DisableSliderHeadTracking.Value; head.JudgeAsNormalHitCircle = !DisableSliderHeadJudgement.Value; - } break; } @@ -71,8 +68,16 @@ namespace osu.Game.Rulesets.Osu.Mods { foreach (var obj in drawables) { - if (obj is DrawableSlider slider) - slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + switch (obj) + { + case DrawableSlider slider: + slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + break; + + case DrawableSliderHead head: + head.TrackFollowCircle = !DisableSliderHeadTracking.Value; + break; + } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 08e9c5eb14..ee1df00ef7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -22,6 +22,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; + /// + /// Makes this track the follow circle when the start time is reached. + /// If false, this will be pinned to its initial position in the slider. + /// + public bool TrackFollowCircle = true; + private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; @@ -66,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(Slider != null); Debug.Assert(HitObject != null); - if (HitObject.TrackFollowCircle) + if (TrackFollowCircle) { double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 13eac60300..28e57567cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -8,12 +8,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderHeadCircle : HitCircle { - /// - /// Makes this track the follow circle when the start time is reached. - /// If false, this will be pinned to its initial position in the slider. - /// - public bool TrackFollowCircle = true; - /// /// Whether to treat this as a normal for judgement purposes. /// If false, judgement will be ignored. diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index e9b4bb416c..8eb2714c04 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSlider.HitObject == null) return; - if (!drawableSlider.HitObject.HeadCircle.TrackFollowCircle) + if (!drawableSlider.HeadCircle.TrackFollowCircle) { // When not tracking the follow circle, force the path to not snake out as it looks better that way. SnakingOut.UnbindFrom(snakingOut); From dad32da4153e8cb18443c6c1f12c1a1847754936 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 17:34:05 +0900 Subject: [PATCH 032/101] Add rate limiting on sending download progress updates --- .../Rooms/OnlinePlayBeatmapAvailablilityTracker.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index dcb366ddab..cfaf43451f 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game.Beatmaps; namespace osu.Game.Online.Rooms @@ -24,12 +25,20 @@ namespace osu.Game.Online.Rooms /// public IBindable Availability => availability; - private readonly Bindable availability = new Bindable(); + private readonly Bindable availability = new Bindable(BeatmapAvailability.LocallyAvailable()); + + private ScheduledDelegate progressUpdate; public OnlinePlayBeatmapAvailablilityTracker() { State.BindValueChanged(_ => updateAvailability()); - Progress.BindValueChanged(_ => updateAvailability(), true); + Progress.BindValueChanged(_ => + { + // incoming progress changes are going to be at a very high rate. + // we don't want to flood the network with this, so rate limit how often we send progress updates. + if (progressUpdate?.Completed != false) + progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); + }); } protected override void LoadComplete() From 2218247b21d09a8317ae84dd4dffa1bbf7a744c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 11:07:50 +0900 Subject: [PATCH 033/101] Override mod type --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8cd6676f9d..863dc05216 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override bool Ranked => false; + public override ModType Type => ModType.Conversion; + [SettingSource("Disable slider head judgement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable DisableSliderHeadJudgement { get; } = new BindableBool(true); From d955200e0718db14fa4b5ea13e6355e5b7134983 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 11:10:07 +0900 Subject: [PATCH 034/101] Prevent invalid hit results for ignored slider heads --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index ee1df00ef7..87cfa47091 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. var result = base.ResultFor(timeOffset); - return result.IsHit() ? HitResult.IgnoreHit : result; + return result.IsHit() ? HitResult.IgnoreHit : HitResult.IgnoreMiss; } public Action OnShake; From 42c169054afa0b0aaf6e84002d4f05fd80e63e17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Feb 2021 17:46:34 +0900 Subject: [PATCH 035/101] Revert "Disable failing test temporarily pending resolution" This reverts commit 10142a44716882a4671d4cae2391a96348bd90ba. --- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 1d13c6229c..2f7e59f800 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual.Multiplayer /// Tests that the same instances are not shared between two playlist items. /// [Test] - [Ignore("Temporarily disabled due to a non-trivial test failure")] public void TestNewItemHasNewModInstances() { AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); From a08c51f213594a02ea3d354c4e913f29723ab2fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 19:23:10 +0900 Subject: [PATCH 036/101] Remove duplicate code --- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index e29fb658a3..ae32295676 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -164,9 +164,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); - userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); - if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); else From 3133ccacfa79704dbf89faad3cb7c82fb242de32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:09:38 +0900 Subject: [PATCH 037/101] Reset selected mods between each test method This doesn't actually fix or change behaviour, but does seem like something we probably want to do here. --- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 2f7e59f800..7d83ba569d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -87,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); + SelectedMods.Value = Array.Empty(); }); AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect())); From be379e0e3cc910b7b3cf1fb6b48460aeaa2673d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:44:11 +0900 Subject: [PATCH 038/101] Change CopyFrom to always overwrite all settings with incoming values --- osu.Game/Rulesets/Mods/Mod.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 3a8717e678..dec72d94e5 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mods } /// - /// Copies mod setting values from into this instance. + /// Copies mod setting values from into this instance, overwriting all existing settings. /// /// The mod to copy properties from. public void CopyFrom(Mod source) @@ -147,9 +147,7 @@ namespace osu.Game.Rulesets.Mods var targetBindable = (IBindable)prop.GetValue(this); var sourceBindable = (IBindable)prop.GetValue(source); - // we only care about changes that have been made away from defaults. - if (!sourceBindable.IsDefault) - CopyAdjustedSetting(targetBindable, sourceBindable); + CopyAdjustedSetting(targetBindable, sourceBindable); } } From 8204d360a8d84f5ac3fe2eec40155999c23a5ba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:44:42 +0900 Subject: [PATCH 039/101] Always reset local user settings when a mod is deselected in ModSelectOverlay --- osu.Game/Overlays/Mods/ModButton.cs | 2 ++ osu.Game/Overlays/Mods/ModSection.cs | 4 +++- osu.Game/Rulesets/Mods/Mod.cs | 5 +++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 11 +++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 8e0d1f5bbd..06f2fea43f 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -69,6 +69,8 @@ namespace osu.Game.Overlays.Mods Mod newSelection = SelectedMod ?? Mods[0]; + newSelection.ResetSettingsToDefaults(); + Schedule(() => { if (beforeSelected != Selected) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index ecbcba7ad3..08bd3f8622 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -197,8 +197,10 @@ namespace osu.Game.Overlays.Mods continue; var buttonMod = button.Mods[index]; - buttonMod.CopyFrom(mod); button.SelectAt(index); + + // the selection above will reset settings to defaults, but as this is an external change we want to copy the new settings across. + buttonMod.CopyFrom(mod); return; } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index dec72d94e5..2a11c92223 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -173,5 +173,10 @@ namespace osu.Game.Rulesets.Mods } public bool Equals(IMod other) => GetType() == other?.GetType(); + + /// + /// Reset all custom settings for this mod back to their defaults. + /// + public virtual void ResetSettingsToDefaults() => CopyFrom((Mod)Activator.CreateInstance(GetType())); } } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index a531e885db..dbc35569e7 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -141,5 +141,16 @@ namespace osu.Game.Rulesets.Mods ApplySetting(DrainRate, dr => difficulty.DrainRate = dr); ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od); } + + public override void ResetSettingsToDefaults() + { + base.ResetSettingsToDefaults(); + + if (difficulty != null) + { + // base implementation potentially overwrite modified defaults that came from a beatmap selection. + TransferSettings(difficulty); + } + } } } From 0efad9ded10e03233ef2f14d644260178d9c746d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:54:13 +0900 Subject: [PATCH 040/101] Add test coverage of setting reset on deselection --- .../UserInterface/TestSceneModSelectOverlay.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 37ebc72984..85350c028c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -47,6 +47,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("show", () => modSelect.Show()); } + [Test] + public void TestSettingsResetOnDeselection() + { + var osuModDoubleTime = new OsuModDoubleTime { SpeedChange = { Value = 1.2 } }; + + changeRuleset(0); + + AddStep("set dt mod with custom rate", () => { SelectedMods.Value = new[] { osuModDoubleTime }; }); + + AddAssert("selected mod matches", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.Value == 1.2); + + AddStep("deselect", () => modSelect.DeselectAllButton.Click()); + AddAssert("selected mods empty", () => SelectedMods.Value.Count == 0); + + AddStep("reselect", () => modSelect.GetModButton(osuModDoubleTime).Click()); + AddAssert("selected mod has default value", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.IsDefault == true); + } + [Test] public void TestAnimationFlushOnClose() { From b96a594546b38a866c7e661b707de04518407baf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 15:11:58 +0900 Subject: [PATCH 041/101] Remove unnecessary initial call to HitObjectApplied bound method Was causing test failures. Looks to be unnecessary on a check of when HitObjectApplied is invoked. --- osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index 8eb2714c04..f9b8ffca7b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -45,7 +45,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; drawableObject.HitObjectApplied += onHitObjectApplied; - onHitObjectApplied(drawableObject); } private void onHitObjectApplied(DrawableHitObject obj) From 695e46a358ba1dd75da161ee70e09bd19d462334 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 15:31:55 +0900 Subject: [PATCH 042/101] Fix AutoPilot mod failing to block touch input --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index c8fe4f41ca..7314021a14 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu protected override bool Handle(UIEvent e) { - if (e is MouseMoveEvent && !AllowUserCursorMovement) return false; + if ((e is MouseMoveEvent || e is TouchMoveEvent) && !AllowUserCursorMovement) return false; return base.Handle(e); } From b87327841dec18cda4edcc9c6ee4a52d4b8ebf29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 15:46:23 +0900 Subject: [PATCH 043/101] Add test covering initial state propagation --- .../TestSceneMultiplayerParticipantsList.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index c3852fafd4..0f7a9b442d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,16 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { [SetUp] - public new void Setup() => Schedule(() => - { - Child = new ParticipantsList - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(380, 0.7f) - }; - }); + public new void Setup() => Schedule(createNewParticipantsList); [Test] public void TestAddUser() @@ -92,6 +83,14 @@ namespace osu.Game.Tests.Visual.Multiplayer checkProgressBarVisibility(true); } + [Test] + public void TestCorrectInitialState() + { + AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddStep("recreate list", createNewParticipantsList); + checkProgressBarVisibility(true); + } + [Test] public void TestBeatmapDownloadingStates() { @@ -212,6 +211,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } } + private void createNewParticipantsList() + { + Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) }; + } + private void checkProgressBarVisibility(bool visible) => AddUntilStep($"progress bar {(visible ? "is" : "is not")}visible", () => this.ChildrenOfType().Single().IsPresent == visible); From 04c243386b136359a81606c612686fe1a8754527 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 16:02:56 +0900 Subject: [PATCH 044/101] Fix initial state transfer regressing --- .../OnlinePlayBeatmapAvailablilityTracker.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index cfaf43451f..d6f4c45a75 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -29,18 +29,6 @@ namespace osu.Game.Online.Rooms private ScheduledDelegate progressUpdate; - public OnlinePlayBeatmapAvailablilityTracker() - { - State.BindValueChanged(_ => updateAvailability()); - Progress.BindValueChanged(_ => - { - // incoming progress changes are going to be at a very high rate. - // we don't want to flood the network with this, so rate limit how often we send progress updates. - if (progressUpdate?.Completed != false) - progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); - }); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -54,6 +42,16 @@ namespace osu.Game.Online.Rooms Model.Value = item.NewValue.Beatmap.Value.BeatmapSet; }, true); + + Progress.BindValueChanged(_ => + { + // incoming progress changes are going to be at a very high rate. + // we don't want to flood the network with this, so rate limit how often we send progress updates. + if (progressUpdate?.Completed != false) + progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); + }); + + State.BindValueChanged(_ => updateAvailability(), true); } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) From b5fa9508006c76decac0752a6bdaf47598196d4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:30:05 +0900 Subject: [PATCH 045/101] Remove unnecessary depth specification --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 4a7318d065..b07f91b9ed 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header.With(h => h.Depth = -float.MaxValue), + Header, content = new Container { RelativeSizeAxes = Axes.X, From 178d88bcf197393fab133d3bd760d33c35b8e3c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:32:44 +0900 Subject: [PATCH 046/101] Change BackgroundColour into a property --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/FullscreenOverlay.cs | 6 +++--- osu.Game/Overlays/UserProfileOverlay.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index cfa0ff00bc..5df7a4650e 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader(); - protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + protected override Color4 BackgroundColour => ColourProvider.Background6; private void onTypingStarted() { diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 723b61bbc5..bdb3715e73 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays protected override BeatmapSetHeader CreateHeader() => new BeatmapSetHeader(); - protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + protected override Color4 BackgroundColour => ColourProvider.Background6; protected override void PopOutComplete() { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 5200b567ff..05bad30107 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays ListingSelected = ShowListing, }; - protected override Color4 GetBackgroundColour() => ColourProvider.Background4; + protected override Color4 BackgroundColour => ColourProvider.Background4; public void ShowListing() { diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index d0a0c994aa..735f0bcbd4 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -23,6 +23,8 @@ namespace osu.Game.Overlays public T Header { get; } + protected virtual Color4 BackgroundColour => ColourProvider.Background5; + [Resolved] protected IAPIProvider API { get; private set; } @@ -59,7 +61,7 @@ namespace osu.Game.Overlays new Box { RelativeSizeAxes = Axes.Both, - Colour = GetBackgroundColour() + Colour = BackgroundColour }, content = new Container { @@ -80,8 +82,6 @@ namespace osu.Game.Overlays [NotNull] protected abstract T CreateHeader(); - protected virtual Color4 GetBackgroundColour() => ColourProvider.Background5; - public override void Show() { if (State.Value == Visibility.Visible) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index ccd9c291c4..299a14b250 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays protected override ProfileHeader CreateHeader() => new ProfileHeader(); - protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + protected override Color4 BackgroundColour => ColourProvider.Background6; public void ShowUser(int userId) => ShowUser(new User { Id = userId }); From 167076663304d009769df70fae7feccdf4128f0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:42:15 +0900 Subject: [PATCH 047/101] Avoid unbinding external events --- osu.Game/Overlays/RankingsOverlay.cs | 44 +++++++++------------- osu.Game/Overlays/TabbableOnlineOverlay.cs | 2 +- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 6cd72d6e2c..a093969115 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -17,8 +17,6 @@ namespace osu.Game.Overlays { protected Bindable Country => Header.Country; - protected Bindable Scope => Header.Current; - private APIRequest lastRequest; [Resolved] @@ -42,31 +40,31 @@ namespace osu.Game.Overlays { // if a country is requested, force performance scope. if (Country.Value != null) - Scope.Value = RankingsScope.Performance; + Header.Current.Value = RankingsScope.Performance; - Scheduler.AddOnce(loadNewContent); - }); - - // Unbind events from scope so base class event will not be called - Scope.UnbindEvents(); - Scope.BindValueChanged(_ => - { - // country filtering is only valid for performance scope. - if (Scope.Value != RankingsScope.Performance) - Country.Value = null; - - Scheduler.AddOnce(loadNewContent); + Scheduler.AddOnce(triggerTabChanged); }); ruleset.BindValueChanged(_ => { - if (Scope.Value == RankingsScope.Spotlights) + if (Header.Current.Value == RankingsScope.Spotlights) return; - Scheduler.AddOnce(loadNewContent); + Scheduler.AddOnce(triggerTabChanged); }); } + protected override void OnTabChanged(RankingsScope tab) + { + // country filtering is only valid for performance scope. + if (Header.Current.Value != RankingsScope.Performance) + Country.Value = null; + + Scheduler.AddOnce(triggerTabChanged); + } + + private void triggerTabChanged() => base.OnTabChanged(Header.Current.Value); + protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); public void ShowCountry(Country requested) @@ -79,17 +77,11 @@ namespace osu.Game.Overlays Country.Value = requested; } - public void ShowSpotlights() - { - Scope.Value = RankingsScope.Spotlights; - Show(); - } - protected override void CreateDisplayToLoad(RankingsScope tab) { lastRequest?.Cancel(); - if (Scope.Value == RankingsScope.Spotlights) + if (Header.Current.Value == RankingsScope.Spotlights) { LoadDisplay(new SpotlightsLayout { @@ -115,7 +107,7 @@ namespace osu.Game.Overlays private APIRequest createScopedRequest() { - switch (Scope.Value) + switch (Header.Current.Value) { case RankingsScope.Performance: return new GetUserRankingsRequest(ruleset.Value, country: Country.Value?.FlagName); @@ -153,8 +145,6 @@ namespace osu.Game.Overlays return null; } - private void loadNewContent() => OnTabChanged(Scope.Value); - protected override void Dispose(bool isDisposing) { lastRequest?.Cancel(); diff --git a/osu.Game/Overlays/TabbableOnlineOverlay.cs b/osu.Game/Overlays/TabbableOnlineOverlay.cs index cbcf3cd96e..8172e99c1b 100644 --- a/osu.Game/Overlays/TabbableOnlineOverlay.cs +++ b/osu.Game/Overlays/TabbableOnlineOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays }, (cancellationToken = new CancellationTokenSource()).Token); } - protected void OnTabChanged(TEnum tab) + protected virtual void OnTabChanged(TEnum tab) { cancellationToken?.Cancel(); Loading.Show(); From 17475e60b0a733344a60a619a3c4fc16f2d9b95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:48:50 +0900 Subject: [PATCH 048/101] Fix missed test scene update --- osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 626f545b91..aff510dd95 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online Add(rankingsOverlay = new TestRankingsOverlay { Country = { BindTarget = countryBindable }, - Scope = { BindTarget = scope }, + Header = { Current = { BindTarget = scope } }, }); } @@ -65,8 +65,6 @@ namespace osu.Game.Tests.Visual.Online private class TestRankingsOverlay : RankingsOverlay { public new Bindable Country => base.Country; - - public new Bindable Scope => base.Scope; } } } From 0a96f4d403cf3be814b10de8e2d7cc3e4e2c335e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:56:27 +0900 Subject: [PATCH 049/101] Avoid assigning null to a non-nullable property --- .../Visual/Online/TestSceneFullscreenOverlay.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs index f8b059e471..dc468bb62d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -53,7 +53,16 @@ namespace osu.Game.Tests.Visual.Online }; } - protected override OverlayHeader CreateHeader() => null; + protected override OverlayHeader CreateHeader() => new TestHeader(); + + internal class TestHeader : OverlayHeader + { + protected override OverlayTitle CreateTitle() => new TestTitle(); + + internal class TestTitle : OverlayTitle + { + } + } } } } From d8d830db6e39a3d590dc3963f10e60a5919727fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 19:46:57 +0900 Subject: [PATCH 050/101] Defer playlist load to improve load time of the now playing overlay --- osu.Game/Overlays/NowPlayingOverlay.cs | 37 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 9beb859f28..f94b41155a 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -84,11 +84,6 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Children = new Drawable[] { - playlist = new PlaylistOverlay - { - RelativeSizeAxes = Axes.X, - Y = player_height + 10, - }, playerContainer = new Container { RelativeSizeAxes = Axes.X, @@ -171,7 +166,7 @@ namespace osu.Game.Overlays Anchor = Anchor.CentreRight, Position = new Vector2(-bottom_black_area_height / 2, 0), Icon = FontAwesome.Solid.Bars, - Action = () => playlist.ToggleVisibility(), + Action = togglePlaylist }, } }, @@ -191,13 +186,35 @@ namespace osu.Game.Overlays }; } + private void togglePlaylist() + { + if (playlist == null) + { + LoadComponentAsync(playlist = new PlaylistOverlay + { + RelativeSizeAxes = Axes.X, + Y = player_height + 10, + }, _ => + { + dragContainer.Add(playlist); + + playlist.BeatmapSets.BindTo(musicController.BeatmapSets); + playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); + + togglePlaylist(); + }); + + return; + } + + if (!beatmap.Disabled) + playlist.ToggleVisibility(); + } + protected override void LoadComplete() { base.LoadComplete(); - playlist.BeatmapSets.BindTo(musicController.BeatmapSets); - playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); - beatmap.BindDisabledChanged(beatmapDisabledChanged, true); musicController.TrackChanged += trackChanged; @@ -306,7 +323,7 @@ namespace osu.Game.Overlays private void beatmapDisabledChanged(bool disabled) { if (disabled) - playlist.Hide(); + playlist?.Hide(); prevButton.Enabled.Value = !disabled; nextButton.Enabled.Value = !disabled; From d9dcf8a042bc99e0161a49088784e06553c8b27e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Feb 2021 20:30:31 +0300 Subject: [PATCH 051/101] Fix incorrect header depth in OnlineOverlay --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index b07f91b9ed..4a7318d065 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header, + Header.With(h => h.Depth = -float.MaxValue), content = new Container { RelativeSizeAxes = Axes.X, From e44667e5e073016b471c13d7cce1427d0db89be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 11:31:34 +0900 Subject: [PATCH 052/101] Use MinValue instead Co-authored-by: Salman Ahmed --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 4a7318d065..7c9f751d3b 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header.With(h => h.Depth = -float.MaxValue), + Header.With(h => h.Depth = float.MinValue), content = new Container { RelativeSizeAxes = Axes.X, From e9ef4aaf88a8908596231461420bc83d5ccc569f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:34:45 +0900 Subject: [PATCH 053/101] Add test covering expectations of external mod changes --- .../TestSceneModSelectOverlay.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 85350c028c..dec9e319ea 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -170,6 +170,31 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestExternallySetModIsReplacedByOverlayInstance() + { + Mod external = new OsuModDoubleTime(); + Mod overlayButtonMod = null; + + changeRuleset(0); + + AddStep("set mod externally", () => { SelectedMods.Value = new[] { external }; }); + + AddAssert("ensure button is selected", () => + { + var button = modSelect.GetModButton(SelectedMods.Value.Single()); + overlayButtonMod = button.SelectedMod; + return overlayButtonMod.GetType() == external.GetType(); + }); + + // Right now, when an external change occurs, the ModSelectOverlay will replace the global instance with its own + AddAssert("mod instance doesn't match", () => external != overlayButtonMod); + + AddAssert("one mod present in global selected", () => SelectedMods.Value.Count == 1); + AddAssert("globally selected matches button's mod instance", () => SelectedMods.Value.Contains(overlayButtonMod)); + AddAssert("globally selected doesn't contain original external change", () => !SelectedMods.Value.Contains(external)); + } + [Test] public void TestNonStacked() { From 52f0f3f3b212fb2d38057073138755b9e73c858b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:38:15 +0900 Subject: [PATCH 054/101] Add a note about SelectedMods behavioural quirks --- osu.Game/OsuGameBase.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 20d88d33f2..d3936ed27e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -98,7 +98,14 @@ namespace osu.Game [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); - // todo: move this to SongSelect once Screen has the ability to unsuspend. + /// + /// The current mod selection for the local user. + /// + /// + /// If a mod select overlay is present, mod instances set to this value are not guaranteed to remain as the provided instance and will be overwritten by a copy. + /// In such a case, changes to settings of a mod will *not* propagate after a mod is added to this collection. + /// As such, all settings should be finalised before adding a mod to this collection. + /// [Cached] [Cached(typeof(IBindable>))] protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); From de8a60435fca3f4644efa5b268848829b3812ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:44:37 +0900 Subject: [PATCH 055/101] Add failing test covering reported breaking case --- .../UserInterface/TestSceneModSelectOverlay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index dec9e319ea..9ca1d4102a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -170,6 +170,20 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestSettingsAreRetainedOnReload() + { + changeRuleset(0); + + AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }); + + AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); + + AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay())); + + AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); + } + [Test] public void TestExternallySetModIsReplacedByOverlayInstance() { From 75bc9f607e30495763ec305fe7f6fae608931754 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:55:15 +0900 Subject: [PATCH 056/101] Rename wrongly named method --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 93fe693937..21ed9af421 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -479,10 +479,10 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) section.UpdateSelectedButtons(selectedMods); - updateMods(); + updateMultiplier(); } - private void updateMods() + private void updateMultiplier() { var multiplier = 1.0; From a39263423c95dd25344dff4e4a5e56afb0842d3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:12:29 +0900 Subject: [PATCH 057/101] Fix externally changed settings from being reset when ModSelectOverlay is initialised --- osu.Game/Overlays/Mods/ModButton.cs | 16 ++++++++++++---- osu.Game/Overlays/Mods/ModSection.cs | 5 +++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 06f2fea43f..5e3733cd5e 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -46,8 +46,9 @@ namespace osu.Game.Overlays.Mods /// Change the selected mod index of this button. /// /// The new index. + /// Whether any settings applied to the mod should be reset on selection. /// Whether the selection changed. - private bool changeSelectedIndex(int newIndex) + private bool changeSelectedIndex(int newIndex, bool resetSettings = true) { if (newIndex == selectedIndex) return false; @@ -69,7 +70,8 @@ namespace osu.Game.Overlays.Mods Mod newSelection = SelectedMod ?? Mods[0]; - newSelection.ResetSettingsToDefaults(); + if (resetSettings) + newSelection.ResetSettingsToDefaults(); Schedule(() => { @@ -211,11 +213,17 @@ namespace osu.Game.Overlays.Mods Deselect(); } - public bool SelectAt(int index) + /// + /// Select the mod at the provided index. + /// + /// The index to select. + /// Whether any settings applied to the mod should be reset on selection. + /// Whether the selection changed. + public bool SelectAt(int index, bool resetSettings = true) { if (!Mods[index].HasImplementation) return false; - changeSelectedIndex(index); + changeSelectedIndex(index, resetSettings); return true; } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 08bd3f8622..71ecef2b82 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -197,9 +197,10 @@ namespace osu.Game.Overlays.Mods continue; var buttonMod = button.Mods[index]; - button.SelectAt(index); - // the selection above will reset settings to defaults, but as this is an external change we want to copy the new settings across. + button.SelectAt(index, false); + + // as this is likely coming from an external change, ensure the settings of the mod are in sync. buttonMod.CopyFrom(mod); return; } From 435c85a2e79b8682d093c261054c8d4ad67215b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:13:09 +0900 Subject: [PATCH 058/101] Avoid executing selection twice on ModSelectOverlay load --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 21ed9af421..f1bfa26a98 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -371,8 +371,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + SelectedMods.BindValueChanged(_ => updateSelectedButtons()); availableMods.BindValueChanged(_ => updateAvailableMods(), true); - SelectedMods.BindValueChanged(_ => updateSelectedButtons(), true); } protected override void PopOut() From 98a83722ff0ad8fffdd426a55a32792d8ce8febd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:29:55 +0900 Subject: [PATCH 059/101] Move the point at which selected mods are reset in tests to allow mutliple creation test flow --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9ca1d4102a..2885dbee00 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -39,7 +39,11 @@ namespace osu.Game.Tests.Visual.UserInterface } [SetUp] - public void SetUp() => Schedule(() => createDisplay(() => new TestModSelectOverlay())); + public void SetUp() => Schedule(() => + { + SelectedMods.Value = Array.Empty(); + createDisplay(() => new TestModSelectOverlay()); + }); [SetUpSteps] public void SetUpSteps() @@ -370,7 +374,6 @@ namespace osu.Game.Tests.Visual.UserInterface private void createDisplay(Func createOverlayFunc) { - SelectedMods.Value = Array.Empty(); Children = new Drawable[] { modSelect = createOverlayFunc().With(d => From 67c1c4c1ebdd89e8c63c76e071f9598c1db32250 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:30:17 +0900 Subject: [PATCH 060/101] Copy settings before applying selection --- osu.Game/Overlays/Mods/ModSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 71ecef2b82..c3e56abd05 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -198,10 +198,10 @@ namespace osu.Game.Overlays.Mods var buttonMod = button.Mods[index]; - button.SelectAt(index, false); - // as this is likely coming from an external change, ensure the settings of the mod are in sync. buttonMod.CopyFrom(mod); + + button.SelectAt(index, false); return; } From b3b0d97354d7c57e554a69ea78f516e0e64833c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:32:57 +0900 Subject: [PATCH 061/101] Avoid potential feedback from bindable event binds --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f1bfa26a98..a6de0ad6b1 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -371,8 +371,11 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - SelectedMods.BindValueChanged(_ => updateSelectedButtons()); availableMods.BindValueChanged(_ => updateAvailableMods(), true); + + // intentionally bound after the above line to avoid a potential update feedback cycle. + // i haven't actually observed this happening but as updateAvailableMods() changes the selection it is plausible. + SelectedMods.BindValueChanged(_ => updateSelectedButtons()); } protected override void PopOut() From 806324b196607d6d918f792289d4043100944904 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 14:53:29 +0900 Subject: [PATCH 062/101] Allow overriding of Overlay pop-in and pop-out samples --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 41fd37a0d7..ee99a39523 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -20,6 +20,8 @@ namespace osu.Game.Graphics.Containers { private SampleChannel samplePopIn; private SampleChannel samplePopOut; + protected virtual string PopInSampleName => "UI/overlay-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-pop-out"; protected override bool BlockNonPositionalInput => true; @@ -40,8 +42,8 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader(true)] private void load(AudioManager audio) { - samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in"); - samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); } protected override void LoadComplete() From 3eda78c363def6589adc0a84aee6c7795cd72d76 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 14:57:46 +0900 Subject: [PATCH 063/101] Use unique samples for Dialog, NowPlaying, SettingsPanel and WaveOverlay pop-in/pop-out --- osu.Game/Overlays/DialogOverlay.cs | 3 +++ osu.Game/Overlays/NowPlayingOverlay.cs | 3 +++ osu.Game/Overlays/SettingsPanel.cs | 2 ++ osu.Game/Overlays/WaveOverlayContainer.cs | 2 ++ 4 files changed, 10 insertions(+) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 9f9dbdbaf1..4cc17a4c14 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -14,6 +14,9 @@ namespace osu.Game.Overlays { private readonly Container dialogContainer; + protected override string PopInSampleName => "UI/dialog-pop-in"; + protected override string PopOutSampleName => "UI/dialog-pop-out"; + public PopupDialog CurrentDialog { get; private set; } public DialogOverlay() diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 5c16a6e5c4..2866d2ad6d 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -51,6 +51,9 @@ namespace osu.Game.Overlays private Container dragContainer; private Container playerContainer; + protected override string PopInSampleName => "UI/now-playing-pop-in"; + protected override string PopOutSampleName => "UI/now-playing-pop-out"; + /// /// Provide a source for the toolbar height. /// diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 7a5a586f67..f1270f750e 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -40,6 +40,8 @@ namespace osu.Game.Overlays private SeekLimitedSearchTextBox searchTextBox; + protected override string PopInSampleName => "UI/settings-pop-in"; + /// /// Provide a source for the toolbar height. /// diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index d0fa9987d5..52ae4dbdbb 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays protected override bool StartHidden => true; + protected override string PopInSampleName => "UI/wave-pop-in"; + protected WaveOverlayContainer() { AddInternal(Waves = new WaveContainer From 22995c216deb26b16ad5e307b35cc5c69ff54260 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 14:59:30 +0900 Subject: [PATCH 064/101] Use unique sample for edit button click (ButtonSystem) --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f400b2114b..c6774127c1 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Menu buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new Button(@"edit", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new Button(@"edit", @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); buttonsTopLevel.Add(new Button(@"browse", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D)); if (host.CanExit) From 73ab1b2b21f2eaaf40884d6d674d22c08031bfd7 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 15:01:22 +0900 Subject: [PATCH 065/101] Add pitch randomisation to HoverSounds on-hover sample playback --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index a1d06711db..21aae1b861 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Framework.Utils; namespace osu.Game.Graphics.UserInterface { @@ -49,9 +50,11 @@ namespace osu.Game.Graphics.UserInterface { bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; - if (enoughTimePassedSinceLastPlayback) + if (enoughTimePassedSinceLastPlayback && sampleHover != null) { - sampleHover?.Play(); + sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08); + sampleHover.Play(); + lastPlaybackTime.Value = Time.Current; } From 4e2ab0bad2c1aed6c9abc75250d1160ae87218f4 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 15:02:17 +0900 Subject: [PATCH 066/101] Use a separate sample set for Toolbar buttons --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 5 ++++- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index a1d06711db..4a79d1fbec 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -68,6 +68,9 @@ namespace osu.Game.Graphics.UserInterface Normal, [Description("-softer")] - Soft + Soft, + + [Description("-toolbar")] + Toolbar } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 49b9c62d85..83f2bdf6cb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Toolbar private KeyBindingStore keyBindings { get; set; } protected ToolbarButton() - : base(HoverSampleSet.Loud) + : base(HoverSampleSet.Toolbar) { Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; From bc7f4a4f881b04feb4ac1c23b2d1fc5aba1fada5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 20:16:22 +0900 Subject: [PATCH 067/101] Use a single sample for CarouselHeader on-hover with randomised pitch instead of multiple samples --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index f1120f55a6..4f53a6e202 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { - sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); + sampleHover = audio.Samples.Get("SongSelect/song-ping"); hoverLayer.Colour = colours.Blue.Opacity(0.1f); } @@ -99,7 +99,11 @@ namespace osu.Game.Screens.Select.Carousel protected override bool OnHover(HoverEvent e) { - sampleHover?.Play(); + if (sampleHover != null) + { + sampleHover.Frequency.Value = 0.90 + RNG.NextDouble(0.2); + sampleHover.Play(); + } hoverLayer.FadeIn(100, Easing.OutQuint); return base.OnHover(e); From 625eb78a118566dc72161da36c9b5997d10309e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 17:59:52 +0900 Subject: [PATCH 068/101] Simplify with an early exit for null sample --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 21aae1b861..5d6d0896fd 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -48,9 +48,12 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { + if (sampleHover == null) + return false; + bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; - if (enoughTimePassedSinceLastPlayback && sampleHover != null) + if (enoughTimePassedSinceLastPlayback) { sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08); sampleHover.Play(); @@ -58,7 +61,7 @@ namespace osu.Game.Graphics.UserInterface lastPlaybackTime.Value = Time.Current; } - return base.OnHover(e); + return false; } } From 996f1098f6e7054e97150421ead8188147f5aa09 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Feb 2021 18:14:32 +0900 Subject: [PATCH 069/101] Use alternate sample on the downbeat while hovering OsuLogo --- osu.Game/Screens/Menu/OsuLogo.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 68d23e1a32..1d0af30275 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -45,6 +45,7 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleClick; private SampleChannel sampleBeat; + private SampleChannel sampleDownbeat; private readonly Container colourAndTriangles; private readonly Triangles triangles; @@ -259,6 +260,7 @@ namespace osu.Game.Screens.Menu { sampleClick = audio.Samples.Get(@"Menu/osu-logo-select"); sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat"); + sampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat"); logo.Texture = textures.Get(@"Menu/logo"); ripple.Texture = textures.Get(@"Menu/logo"); @@ -281,7 +283,15 @@ namespace osu.Game.Screens.Menu if (beatIndex < 0) return; if (IsHovered) - this.Delay(early_activation).Schedule(() => sampleBeat.Play()); + { + this.Delay(early_activation).Schedule(() => + { + if (beatIndex % (int)timingPoint.TimeSignature == 0) + sampleDownbeat.Play(); + else + sampleBeat.Play(); + }); + } logoBeatContainer .ScaleTo(1 - 0.02f * amplitudeAdjust, early_activation, Easing.Out).Then() From cf06684ad121d58548d6cf0b9e87a439167dbc57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:38:31 +0900 Subject: [PATCH 070/101] Judge heads as slider ticks instead --- .../Judgements/SliderTickJudgement.cs | 12 ++++++++++++ .../Objects/Drawables/DrawableSliderHead.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 5 ----- 4 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs new file mode 100644 index 0000000000..a088696784 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class SliderTickJudgement : OsuJudgement + { + public override HitResult MaxResult => HitResult.LargeTickHit; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 87cfa47091..c3759b6a34 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. var result = base.ResultFor(timeOffset); - return result.IsHit() ? HitResult.IgnoreHit : HitResult.IgnoreMiss; + return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; } public Action OnShake; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 28e57567cb..5672283230 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -10,10 +10,10 @@ namespace osu.Game.Rulesets.Osu.Objects { /// /// Whether to treat this as a normal for judgement purposes. - /// If false, judgement will be ignored. + /// If false, this will be judged as a instead. /// public bool JudgeAsNormalHitCircle = true; - public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new OsuIgnoreJudgement(); + public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new SliderTickJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index a427ee1955..725dbe81fb 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -33,10 +33,5 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; public override Judgement CreateJudgement() => new SliderTickJudgement(); - - public class SliderTickJudgement : OsuJudgement - { - public override HitResult MaxResult => HitResult.LargeTickHit; - } } } From 6730c4c58b22ce2e753ac0f4b7055b5b87f62cde Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:41:28 +0900 Subject: [PATCH 071/101] Apply review comments (user explanations + property names) --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 863dc05216..642da87693 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -32,27 +32,27 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Conversion; - [SettingSource("Disable slider head judgement", "Scores sliders proportionally to the number of ticks hit.")] - public Bindable DisableSliderHeadJudgement { get; } = new BindableBool(true); + [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] + public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); - [SettingSource("Disable slider head tracking", "Pins slider heads at their starting position, regardless of time.")] - public Bindable DisableSliderHeadTracking { get; } = new BindableBool(true); + [SettingSource("No slider head movement", "Pins slider heads at their starting position, regardless of time.")] + public Bindable NoSliderHeadMovement { get; } = new BindableBool(true); - [SettingSource("Disable note lock lenience", "Applies note lock to the full hit window.")] - public Bindable DisableLenientNoteLock { get; } = new BindableBool(true); + [SettingSource("Apply classic note lock", "Applies note lock to the full hit window.")] + public Bindable ClassicNoteLock { get; } = new BindableBool(true); - [SettingSource("Disable exact slider follow circle tracking", "Makes the slider follow circle track its final size at all times.")] - public Bindable DisableExactFollowCircleTracking { get; } = new BindableBool(true); + [SettingSource("Use fixed slider follow circle hit area", "Makes the slider follow circle track its final size at all times.")] + public Bindable FixedFollowCircleHitArea { get; } = new BindableBool(true); public void ApplyToHitObject(HitObject hitObject) { switch (hitObject) { case Slider slider: - slider.IgnoreJudgement = !DisableSliderHeadJudgement.Value; + slider.IgnoreJudgement = !NoSliderHeadAccuracy.Value; foreach (var head in slider.NestedHitObjects.OfType()) - head.JudgeAsNormalHitCircle = !DisableSliderHeadJudgement.Value; + head.JudgeAsNormalHitCircle = !NoSliderHeadAccuracy.Value; break; } @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var osuRuleset = (DrawableOsuRuleset)drawableRuleset; - if (!DisableLenientNoteLock.Value) + if (!ClassicNoteLock.Value) osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); } @@ -73,11 +73,11 @@ namespace osu.Game.Rulesets.Osu.Mods switch (obj) { case DrawableSlider slider: - slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + slider.Ball.TrackVisualSize = !FixedFollowCircleHitArea.Value; break; case DrawableSliderHead head: - head.TrackFollowCircle = !DisableSliderHeadTracking.Value; + head.TrackFollowCircle = !NoSliderHeadMovement.Value; break; } } From 18a29dcb9664075e4fd1dadfc57157cbcc2fc21a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:42:13 +0900 Subject: [PATCH 072/101] Rename bindable member, reorder binds --- osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index f9b8ffca7b..b9cd176c63 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } - private readonly Bindable snakingOut = new Bindable(); + private readonly Bindable configSnakingOut = new Bindable(); [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) @@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default accentColour = drawableObject.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true); - SnakingOut.BindTo(snakingOut); config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); - config?.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut); + + SnakingOut.BindTo(configSnakingOut); BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; @@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (!drawableSlider.HeadCircle.TrackFollowCircle) { // When not tracking the follow circle, force the path to not snake out as it looks better that way. - SnakingOut.UnbindFrom(snakingOut); + SnakingOut.UnbindFrom(configSnakingOut); SnakingOut.Value = false; } } From 9519b7f7c16e234d119ecaf65e3ff019ad84c400 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:43:14 +0900 Subject: [PATCH 073/101] Adjust comment --- osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index b9cd176c63..4dd7b2d69c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSlider.HitObject == null) return; + // When not tracking the follow circle, unbind from the config and forcefully disable snaking out - it looks better that way. if (!drawableSlider.HeadCircle.TrackFollowCircle) { - // When not tracking the follow circle, force the path to not snake out as it looks better that way. SnakingOut.UnbindFrom(configSnakingOut); SnakingOut.Value = false; } From 2fcc4213e16f8a9ebc33d91474fe3c0a632cf36c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:46:26 +0900 Subject: [PATCH 074/101] Rename IgnoreJudgement -> OnlyJudgeNestedObjects --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 642da87693..8e533854c0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case Slider slider: - slider.IgnoreJudgement = !NoSliderHeadAccuracy.Value; + slider.OnlyJudgeNestedObjects = !NoSliderHeadAccuracy.Value; foreach (var head in slider.NestedHitObjects.OfType()) head.JudgeAsNormalHitCircle = !NoSliderHeadAccuracy.Value; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index e607163b3e..13057d7a9a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SliderBall Ball { get; private set; } public SkinnableDrawable Body { get; private set; } - public override bool DisplayResult => !HitObject.IgnoreJudgement; + public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects; private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; @@ -250,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < HitObject.EndTime) return; - if (HitObject.IgnoreJudgement) + if (HitObject.OnlyJudgeNestedObjects) { ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); return; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 01694a838b..332163454a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -115,10 +115,10 @@ namespace osu.Game.Rulesets.Osu.Objects public double TickDistanceMultiplier = 1; /// - /// Whether this 's judgement should be ignored. - /// If false, this will be judged proportionally to the number of ticks hit. + /// Whether this 's judgement is fully handled by its nested s. + /// If false, this will be judged proportionally to the number of nested s hit. /// - public bool IgnoreJudgement = true; + public bool OnlyJudgeNestedObjects = true; [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects HeadCircle.Samples = this.GetNodeSamples(0); } - public override Judgement CreateJudgement() => IgnoreJudgement ? new OsuIgnoreJudgement() : new OsuJudgement(); + public override Judgement CreateJudgement() => OnlyJudgeNestedObjects ? new OsuIgnoreJudgement() : new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } From a16f4cee3a0d44bbcdf69adb4949f2d3a425efd1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:52:39 +0900 Subject: [PATCH 075/101] Adjust DrawableSlider comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 13057d7a9a..921139c4e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -250,13 +250,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < HitObject.EndTime) return; + // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. + // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). if (HitObject.OnlyJudgeNestedObjects) { ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); return; } - // If not ignoring judgement, score proportionally based on the number of ticks hit, counting the head circle as a tick. + // Otherwise, if this slider is also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. ApplyResult(r => { int totalTicks = NestedHitObjects.Count; From 6bf40170db22f31cea0c040824a218794d236718 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:53:23 +0900 Subject: [PATCH 076/101] Rename SliderBall flag --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8e533854c0..17b0b18b52 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (obj) { case DrawableSlider slider: - slider.Ball.TrackVisualSize = !FixedFollowCircleHitArea.Value; + slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value; break; case DrawableSliderHead head: diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index da3debbd42..82b677e12c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// Whether to track accurately to the visual size of this . /// If false, tracking will be performed at the final scale at all times. /// - public bool TrackVisualSize = true; + public bool InputTracksVisualSize = true; private readonly Drawable followCircle; private readonly DrawableSlider drawableSlider; @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - if (TrackVisualSize) + if (InputTracksVisualSize) followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); else { From 0dcdad98397453b439cab67421e29ecb3ca13f00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:04:23 +0900 Subject: [PATCH 077/101] Adjust comment for DrawableSliderHead --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index c3759b6a34..01c0d988ee 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (HitObject.JudgeAsNormalHitCircle) return base.ResultFor(timeOffset); - // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. + // If not judged as a normal hitcircle, judge as a slider tick instead. This is the classic osu!stable scoring. var result = base.ResultFor(timeOffset); return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; } From 393cd6c74a354919dc0b410d0bea38b8701bd032 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:39:47 +0900 Subject: [PATCH 078/101] Add helper class for tracking changes to mod settings --- .../Configuration/SettingSourceAttribute.cs | 27 ++++++++++++++++ .../Screens/Select/Details/AdvancedStats.cs | 31 ++++++------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 50069be4b2..00c322065a 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -9,6 +9,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; namespace osu.Game.Configuration { @@ -140,4 +141,30 @@ namespace osu.Game.Configuration return orderedRelative.Concat(unordered); } } + + public class ModSettingChangeTracker : IDisposable + { + public Action SettingChanged; + + private readonly List references = new List(); + + public ModSettingChangeTracker(IEnumerable mods) + { + foreach (var mod in mods) + { + foreach (var setting in mod.CreateSettingsControls().OfType()) + { + setting.SettingChanged += () => SettingChanged?.Invoke(mod); + references.Add(setting); + } + } + } + + public void Dispose() + { + foreach (var r in references) + r.Dispose(); + references.Clear(); + } + } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 44d908fc46..7966ec4240 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -18,7 +18,6 @@ using System.Threading; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets; namespace osu.Game.Screens.Select.Details @@ -83,32 +82,22 @@ namespace osu.Game.Screens.Select.Details mods.BindValueChanged(modsChanged, true); } - private readonly List references = new List(); + private ModSettingChangeTracker settingChangeTracker; + private ScheduledDelegate debouncedStatisticsUpdate; private void modsChanged(ValueChangedEvent> mods) { - // TODO: find a more permanent solution for this if/when it is needed in other components. - // this is generating drawables for the only purpose of storing bindable references. - foreach (var r in references) - r.Dispose(); + settingChangeTracker?.Dispose(); - references.Clear(); - - ScheduledDelegate debounce = null; - - foreach (var mod in mods.NewValue.OfType()) + settingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + settingChangeTracker.SettingChanged += m => { - foreach (var setting in mod.CreateSettingsControls().OfType()) - { - setting.SettingChanged += () => - { - debounce?.Cancel(); - debounce = Scheduler.AddDelayed(updateStatistics, 100); - }; + if (!(m is IApplicableToDifficulty)) + return; - references.Add(setting); - } - } + debouncedStatisticsUpdate?.Cancel(); + debouncedStatisticsUpdate = Scheduler.AddDelayed(updateStatistics, 100); + }; updateStatistics(); } From 7827e991b2dda30e265c3fc210e3828698d9d00b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:43:39 +0900 Subject: [PATCH 079/101] Also clear event on dispose --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 00c322065a..04b8f8e962 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -162,6 +162,8 @@ namespace osu.Game.Configuration public void Dispose() { + SettingChanged = null; + foreach (var r in references) r.Dispose(); references.Clear(); From 822c66033f0d9cb2c2dbee0d138cdb67bc1fdd3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:56:59 +0900 Subject: [PATCH 080/101] Add local-user freemod configuration --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 9 +++++++- .../OnlinePlay/FreeModSelectOverlay.cs | 5 +++-- .../Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 21 +++++++++++++++---- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 93fe693937..488c0659f4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -51,6 +51,11 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool Stacked => true; + /// + /// Whether configurable s can be configured by the local user. + /// + protected virtual bool AllowConfiguration => true; + [NotNull] private Func isValidMod = m => true; @@ -300,6 +305,7 @@ namespace osu.Game.Overlays.Mods Text = "Customisation", Action = () => ModSettingsContainer.ToggleVisibility(), Enabled = { Value = false }, + Alpha = AllowConfiguration ? 1 : 0, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, @@ -509,7 +515,8 @@ namespace osu.Game.Overlays.Mods OnModSelected(selectedMod); - if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show(); + if (selectedMod.RequiresConfiguration && AllowConfiguration) + ModSettingsContainer.Show(); } else { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 7bc226bb3f..ab7be13479 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -20,17 +20,18 @@ namespace osu.Game.Screens.OnlinePlay { protected override bool Stacked => false; + protected override bool AllowConfiguration => false; + public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && !m.RequiresConfiguration && !(m is ModAutoplay) && value(m); + set => base.IsValidMod = m => m.HasImplementation && !(m is ModAutoplay) && value(m); } public FreeModSelectOverlay() { IsValidMod = m => true; - CustomiseButton.Alpha = 0; MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 84e8849726..f17d97c3fd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -59,6 +59,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust) && !mod.RequiresConfiguration; + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 5f2f1366f7..7edba3231c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -12,6 +12,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Threading; +using osu.Game.Configuration; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; @@ -314,12 +316,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private ModSettingChangeTracker modSettingChangeTracker; + private ScheduledDelegate debouncedModSettingsUpdate; + private void onUserModsChanged(ValueChangedEvent> mods) { + modSettingChangeTracker?.Dispose(); + if (client.Room == null) return; client.ChangeUserMods(mods.NewValue); + + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += onModSettingsChanged; + } + + private void onModSettingsChanged(Mod mod) + { + // Debounce changes to mod settings so as to not thrash the network. + debouncedModSettingsUpdate?.Cancel(); + debouncedModSettingsUpdate = Scheduler.AddDelayed(() => client.ChangeUserMods(UserMods.Value), 500); } private void updateBeatmapAvailability(ValueChangedEvent availability) @@ -389,10 +406,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private class UserModSelectOverlay : LocalPlayerModSelectOverlay { - public UserModSelectOverlay() - { - CustomiseButton.Alpha = 0; - } } } } From 4a405bb8598a8dcdbc6772eaac801c9fa4907956 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:04:16 +0900 Subject: [PATCH 081/101] Split ModSettingChangeTracker into separate file --- .../Configuration/ModSettingChangeTracker.cs | 39 +++++++++++++++++++ .../Configuration/SettingSourceAttribute.cs | 29 -------------- 2 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Configuration/ModSettingChangeTracker.cs diff --git a/osu.Game/Configuration/ModSettingChangeTracker.cs b/osu.Game/Configuration/ModSettingChangeTracker.cs new file mode 100644 index 0000000000..f702a2fc22 --- /dev/null +++ b/osu.Game/Configuration/ModSettingChangeTracker.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Configuration +{ + public class ModSettingChangeTracker : IDisposable + { + public Action SettingChanged; + + private readonly List references = new List(); + + public ModSettingChangeTracker(IEnumerable mods) + { + foreach (var mod in mods) + { + foreach (var setting in mod.CreateSettingsControls().OfType()) + { + setting.SettingChanged += () => SettingChanged?.Invoke(mod); + references.Add(setting); + } + } + } + + public void Dispose() + { + SettingChanged = null; + + foreach (var r in references) + r.Dispose(); + references.Clear(); + } + } +} diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 04b8f8e962..50069be4b2 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Mods; namespace osu.Game.Configuration { @@ -141,32 +140,4 @@ namespace osu.Game.Configuration return orderedRelative.Concat(unordered); } } - - public class ModSettingChangeTracker : IDisposable - { - public Action SettingChanged; - - private readonly List references = new List(); - - public ModSettingChangeTracker(IEnumerable mods) - { - foreach (var mod in mods) - { - foreach (var setting in mod.CreateSettingsControls().OfType()) - { - setting.SettingChanged += () => SettingChanged?.Invoke(mod); - references.Add(setting); - } - } - } - - public void Dispose() - { - SettingChanged = null; - - foreach (var r in references) - r.Dispose(); - references.Clear(); - } - } } From 6fff7c39daca9c27c39511b84223e7e98172325b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:09:45 +0900 Subject: [PATCH 082/101] Ensure tracker is disposed --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 ++ osu.Game/Screens/Select/Details/AdvancedStats.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 7edba3231c..59418cb348 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -402,6 +402,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; + + modSettingChangeTracker?.Dispose(); } private class UserModSelectOverlay : LocalPlayerModSelectOverlay diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 7966ec4240..4a03c5e614 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -162,6 +162,7 @@ namespace osu.Game.Screens.Select.Details protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + settingChangeTracker?.Dispose(); starDifficultyCancellationSource?.Cancel(); } From 169acb42de35e88c8755a5140808cb3563cfb97d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:11:36 +0900 Subject: [PATCH 083/101] Xmldoc + cleanup --- .../Configuration/ModSettingChangeTracker.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/ModSettingChangeTracker.cs b/osu.Game/Configuration/ModSettingChangeTracker.cs index f702a2fc22..e2ade7dc6a 100644 --- a/osu.Game/Configuration/ModSettingChangeTracker.cs +++ b/osu.Game/Configuration/ModSettingChangeTracker.cs @@ -9,12 +9,25 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Configuration { + /// + /// A helper class for tracking changes to the settings of a set of s. + /// + /// + /// Ensure to dispose when usage is finished. + /// public class ModSettingChangeTracker : IDisposable { + /// + /// Notifies that the setting of a has changed. + /// public Action SettingChanged; - private readonly List references = new List(); + private readonly List settings = new List(); + /// + /// Creates a new for a set of s. + /// + /// The set of s whose settings need to be tracked. public ModSettingChangeTracker(IEnumerable mods) { foreach (var mod in mods) @@ -22,7 +35,7 @@ namespace osu.Game.Configuration foreach (var setting in mod.CreateSettingsControls().OfType()) { setting.SettingChanged += () => SettingChanged?.Invoke(mod); - references.Add(setting); + settings.Add(setting); } } } @@ -31,9 +44,9 @@ namespace osu.Game.Configuration { SettingChanged = null; - foreach (var r in references) + foreach (var r in settings) r.Dispose(); - references.Clear(); + settings.Clear(); } } } From 86682cdb3480e711d2ecdeab04926c7488a17046 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:16:26 +0900 Subject: [PATCH 084/101] Add client/room null check --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 59418cb348..b7adb71e2f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -336,7 +336,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // Debounce changes to mod settings so as to not thrash the network. debouncedModSettingsUpdate?.Cancel(); - debouncedModSettingsUpdate = Scheduler.AddDelayed(() => client.ChangeUserMods(UserMods.Value), 500); + debouncedModSettingsUpdate = Scheduler.AddDelayed(() => + { + if (client.Room == null) + return; + + client.ChangeUserMods(UserMods.Value); + }, 500); } private void updateBeatmapAvailability(ValueChangedEvent availability) From c458c4cfaee1d9a830e8ddafa512d30d35b5b4cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:27:47 +0900 Subject: [PATCH 085/101] Fix unintended changes due to renaming or otherwise --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 17b0b18b52..5470d0fcb4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var osuRuleset = (DrawableOsuRuleset)drawableRuleset; - if (!ClassicNoteLock.Value) + if (ClassicNoteLock.Value) osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 189ef2d76c..b1069149f3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - HitPolicy = new ObjectOrderedHitPolicy(); + HitPolicy = new StartTimeOrderedHitPolicy(); var hitWindows = new OsuHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) From 321ca43b61a2a47857e04a117d622da6af385492 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:28:00 +0900 Subject: [PATCH 086/101] Update test --- .../TestSceneObjectOrderedHitPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 039a4f142f..77a68b714b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Great); - addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } @@ -291,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.Great); - addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } From 4a391ce03d84adf76adb4214993c28e15cdebdbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 21:24:41 +0900 Subject: [PATCH 087/101] Fix div-by-0 when 0 ticks are hit --- .../Objects/Drawables/DrawableSlider.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 921139c4e9..d35da64ad5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -263,16 +263,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { int totalTicks = NestedHitObjects.Count; int hitTicks = NestedHitObjects.Count(h => h.IsHit); - double hitFraction = (double)totalTicks / hitTicks; if (hitTicks == totalTicks) r.Type = HitResult.Great; - else if (hitFraction >= 0.5) - r.Type = HitResult.Ok; - else if (hitFraction > 0) - r.Type = HitResult.Meh; - else + else if (hitTicks == 0) r.Type = HitResult.Miss; + else + { + double hitFraction = (double)totalTicks / hitTicks; + + if (hitFraction >= 0.5) + r.Type = HitResult.Ok; + else if (hitFraction > 0) + r.Type = HitResult.Meh; + } }); } From 1d425b83224ff7ac765b9af6d16389e637f1d840 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 21:25:31 +0900 Subject: [PATCH 088/101] Simplify case --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index d35da64ad5..847011850c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -271,11 +271,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else { double hitFraction = (double)totalTicks / hitTicks; - - if (hitFraction >= 0.5) - r.Type = HitResult.Ok; - else if (hitFraction > 0) - r.Type = HitResult.Meh; + r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; } }); } From bd2486e5a04bda5fb2a0ff32df6d2dce8681f2f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 21:27:12 +0900 Subject: [PATCH 089/101] Fix grammatical error in comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 847011850c..253b9800a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; } - // Otherwise, if this slider is also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. + // Otherwise, if this slider also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. ApplyResult(r => { int totalTicks = NestedHitObjects.Count; From 20a6405fd20a6cef1251eebfd7435928344679a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 22:06:19 +0900 Subject: [PATCH 090/101] Add explanatory comments + const --- .../Drawables/Connections/FollowPointConnection.cs | 6 ++++-- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 40154ca84c..5541d0e790 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -110,8 +110,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double startTime = start.GetEndTime(); double duration = end.StartTime - startTime; - // For now, adjust the pre-empt for approach rates > 10. - double preempt = PREEMPT * Math.Min(1, start.TimePreempt / 450); + // Preempt time can go below 800ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear preempt function (see: OsuHitObject). + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + double preempt = PREEMPT * Math.Min(1, start.TimePreempt / OsuHitObject.PREEMPT_MIN); fadeOutTime = startTime + fraction * duration; fadeInTime = fadeOutTime - preempt; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 6d28a576a4..22b64af3df 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal const float BASE_SCORING_DISTANCE = 100; + /// + /// Minimum preempt time at AR=10. + /// + public const double PREEMPT_MIN = 450; + public double TimePreempt = 600; public double TimeFadeIn = 400; @@ -113,8 +118,13 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - TimeFadeIn = 400 * Math.Min(1, TimePreempt / 450); + TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); + + // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. + TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } From 5d1d6ec1cbeef3ff0cf17b9e04d556e01772de1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 22:09:24 +0900 Subject: [PATCH 091/101] Fix inverted calculation --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 253b9800a6..9122f347d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables r.Type = HitResult.Miss; else { - double hitFraction = (double)totalTicks / hitTicks; + double hitFraction = (double)hitTicks / totalTicks; r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; } }); From 07b661e28c2d2267dbe1a431f1a75e1d60fc6620 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 23:44:06 +0900 Subject: [PATCH 092/101] Add Messagepack support for serialising unknown bindable types --- .../TestAPIModMessagePackSerialization.cs | 27 +++++++++++++++++++ .../API/ModSettingsDictionaryFormatter.cs | 8 ++++++ 2 files changed, 35 insertions(+) diff --git a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs index 4294f89397..74db477cfc 100644 --- a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs @@ -68,6 +68,16 @@ namespace osu.Game.Tests.Online Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); } + [Test] + public void TestDeserialiseEnumMod() + { + var apiMod = new APIMod(new TestModEnum { TestSetting = { Value = TestEnum.Value2 } }); + + var deserialized = MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(apiMod)); + + Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(1)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => new Mod[] @@ -135,5 +145,22 @@ namespace osu.Game.Tests.Online Value = true }; } + + private class TestModEnum : Mod + { + public override string Name => "Test Mod"; + public override string Acronym => "TM"; + public override double ScoreMultiplier => 1; + + [SettingSource("Test")] + public Bindable TestSetting { get; } = new Bindable(); + } + + private enum TestEnum + { + Value1 = 0, + Value2 = 1, + Value3 = 2 + } } } diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index 99e87677fa..dd854acc32 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using MessagePack; using MessagePack.Formatters; @@ -41,6 +42,13 @@ namespace osu.Game.Online.API primitiveFormatter.Serialize(ref writer, b.Value, options); break; + case IBindable u: + // A mod with unknown (e.g. enum) generic type. + var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); + Debug.Assert(valueMethod != null); + primitiveFormatter.Serialize(ref writer, valueMethod.GetValue(u), options); + break; + default: // fall back for non-bindable cases. primitiveFormatter.Serialize(ref writer, kvp.Value, options); From 63e6ec1c9fa3b0ecb12ecbd2f389edc0042de939 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 20:06:55 +0900 Subject: [PATCH 093/101] Add audio feedback for OSD value changes --- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 46 ++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index 8e8a99a0a7..c5a289a85c 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -19,6 +21,15 @@ namespace osu.Game.Overlays.OSD { private const int lights_bottom_margin = 40; + private readonly int optionCount; + private readonly int selectedOption = -1; + + private SampleChannel sampleOn; + private SampleChannel sampleOff; + private SampleChannel sampleChange; + + private bool playedSample; + public TrackedSettingToast(SettingDescription description) : base(description.Name, description.Value, description.Shortcut) { @@ -46,9 +57,6 @@ namespace osu.Game.Overlays.OSD } }; - int optionCount = 0; - int selectedOption = -1; - switch (description.RawValue) { case bool val: @@ -69,6 +77,38 @@ namespace osu.Game.Overlays.OSD optionLights.Add(new OptionLight { Glowing = i == selectedOption }); } + protected override void Update() + { + base.Update(); + + if (playedSample) return; + + if (optionCount == 1) + { + if (selectedOption == 0) + sampleOn?.Play(); + else + sampleOff?.Play(); + } + else + { + if (sampleChange == null) return; + + sampleChange.Frequency.Value = 1 + (double)selectedOption / (optionCount - 1) * 0.25f; + sampleChange.Play(); + } + + playedSample = true; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleOn = audio.Samples.Get("UI/osd-on"); + sampleOff = audio.Samples.Get("UI/osd-off"); + sampleChange = audio.Samples.Get("UI/osd-change"); + } + private class OptionLight : Container { private Color4 glowingColour, idleColour; From c12c09ec4d497ad7e52a3c4b625e3793dc23d8a1 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Feb 2021 09:57:14 +0900 Subject: [PATCH 094/101] Move logic to LoadComplete instead --- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index c5a289a85c..d61180baa2 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -28,8 +28,6 @@ namespace osu.Game.Overlays.OSD private SampleChannel sampleOff; private SampleChannel sampleChange; - private bool playedSample; - public TrackedSettingToast(SettingDescription description) : base(description.Name, description.Value, description.Shortcut) { @@ -77,11 +75,9 @@ namespace osu.Game.Overlays.OSD optionLights.Add(new OptionLight { Glowing = i == selectedOption }); } - protected override void Update() + protected override void LoadComplete() { - base.Update(); - - if (playedSample) return; + base.LoadComplete(); if (optionCount == 1) { @@ -97,8 +93,6 @@ namespace osu.Game.Overlays.OSD sampleChange.Frequency.Value = 1 + (double)selectedOption / (optionCount - 1) * 0.25f; sampleChange.Play(); } - - playedSample = true; } [BackgroundDependencyLoader] From a1ae739a6235f72995ec460d93874eac2a504aa6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 15:21:50 +0900 Subject: [PATCH 095/101] Add support for Notification sounds --- .../Overlays/Notifications/Notification.cs | 24 ++++++++++++++++++- .../Notifications/NotificationSection.cs | 7 +++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 2dc6b39a92..86e409d0f6 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -3,6 +3,8 @@ 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.Colour; @@ -40,6 +42,11 @@ namespace osu.Game.Overlays.Notifications /// public virtual bool DisplayOnTop => true; + private SampleChannel samplePopIn; + private SampleChannel samplePopOut; + protected virtual string PopInSampleName => "UI/notification-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-pop-out"; // TODO: replace with a unique sample? + protected NotificationLight Light; private readonly CloseButton closeButton; protected Container IconContent; @@ -120,6 +127,13 @@ namespace osu.Game.Overlays.Notifications }); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); + } + protected override bool OnHover(HoverEvent e) { closeButton.FadeIn(75); @@ -143,6 +157,9 @@ namespace osu.Game.Overlays.Notifications protected override void LoadComplete() { base.LoadComplete(); + + samplePopIn?.Play(); + this.FadeInFromZero(200); NotificationContent.MoveToX(DrawSize.X); NotificationContent.MoveToX(0, 500, Easing.OutQuint); @@ -150,12 +167,17 @@ namespace osu.Game.Overlays.Notifications public bool WasClosed; - public virtual void Close() + public virtual void Close() => Close(true); + + public virtual void Close(bool playSound) { if (WasClosed) return; WasClosed = true; + if (playSound) + samplePopOut?.Play(); + Closed?.Invoke(); this.FadeOut(100); Expire(); diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index c2a958b65e..c8cee22370 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -109,7 +109,12 @@ namespace osu.Game.Overlays.Notifications private void clearAll() { - notifications.Children.ForEach(c => c.Close()); + bool playSound = true; + notifications.Children.ForEach(c => + { + c.Close(playSound); + playSound = false; + }); } protected override void Update() From 2ee634d173647a97fa9e5ef40bfe1e262920162b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 21 Jan 2021 18:42:53 +0900 Subject: [PATCH 096/101] Create subclass for "Error" notifications to allow them to have a unique pop-in sound --- .../TestSceneNotificationOverlay.cs | 20 ++++++++++++++++++- osu.Game/OsuGame.cs | 2 +- .../Notifications/SimpleErrorNotification.cs | 17 ++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/Notifications/SimpleErrorNotification.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 43ba23e6c6..d0f6f3fe47 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -105,6 +105,15 @@ namespace osu.Game.Tests.Visual.UserInterface checkDisplayedCount(3); } + [Test] + public void TestError() + { + setState(Visibility.Visible); + AddStep(@"error #1", sendErrorNotification); + AddAssert("Is visible", () => notificationOverlay.State.Value == Visibility.Visible); + checkDisplayedCount(1); + } + [Test] public void TestSpam() { @@ -179,7 +188,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void sendBarrage() { - switch (RNG.Next(0, 4)) + switch (RNG.Next(0, 5)) { case 0: sendHelloNotification(); @@ -196,6 +205,10 @@ namespace osu.Game.Tests.Visual.UserInterface case 3: sendDownloadProgress(); break; + + case 4: + sendErrorNotification(); + break; } } @@ -214,6 +227,11 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.Post(new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); } + private void sendErrorNotification() + { + notificationOverlay.Post(new SimpleErrorNotification { Text = @"Rut roh!. Something went wrong!" }); + } + private void sendManyNotifications() { for (int i = 0; i < 10; i++) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1a1f7bd233..0dc63dcd4b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -778,7 +778,7 @@ namespace osu.Game if (recentLogCount < short_term_display_limit) { - Schedule(() => notifications.Post(new SimpleNotification + Schedule(() => notifications.Post(new SimpleErrorNotification { Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb, Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), diff --git a/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs new file mode 100644 index 0000000000..13c9c5a02d --- /dev/null +++ b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs @@ -0,0 +1,17 @@ +// 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.Graphics.Sprites; + +namespace osu.Game.Overlays.Notifications +{ + public class SimpleErrorNotification : SimpleNotification + { + protected override string PopInSampleName => "UI/error-notification-pop-in"; + + public SimpleErrorNotification() + { + Icon = FontAwesome.Solid.Bomb; + } + } +} From 72562070bcea0718bd6ddce220e354d934b6a987 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:18:00 +0900 Subject: [PATCH 097/101] Remove second overload of Close (makes the call structure hard to follow / invoke correctly) --- osu.Game/Overlays/Notifications/Notification.cs | 6 ++---- osu.Game/Overlays/Notifications/ProgressNotification.cs | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 86e409d0f6..daf931bc24 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Notifications closeButton = new CloseButton { Alpha = 0, - Action = Close, + Action = () => Close(), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding @@ -167,9 +167,7 @@ namespace osu.Game.Overlays.Notifications public bool WasClosed; - public virtual void Close() => Close(true); - - public virtual void Close(bool playSound) + public virtual void Close(bool playSound = true) { if (WasClosed) return; diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 3105ecd742..703c14af2b 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -150,12 +150,12 @@ namespace osu.Game.Overlays.Notifications colourCancelled = colours.Red; } - public override void Close() + public override void Close(bool playSound = true) { switch (State) { case ProgressNotificationState.Cancelled: - base.Close(); + base.Close(playSound); break; case ProgressNotificationState.Active: From 896f318b56866306e455d92c4443c252723d75f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:18:40 +0900 Subject: [PATCH 098/101] Rename variable for clarity --- osu.Game/Overlays/Notifications/NotificationSection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index c8cee22370..38ba712254 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -109,11 +109,11 @@ namespace osu.Game.Overlays.Notifications private void clearAll() { - bool playSound = true; + bool first = true; notifications.Children.ForEach(c => { - c.Close(playSound); - playSound = false; + c.Close(first); + first = false; }); } From 800f12a358b234f7d3409e843c5b913cc563f4b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:19:48 +0900 Subject: [PATCH 099/101] Update resources --- 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 7060e88026..a522a5f43d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f866b232d8..f69613cfd3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 22d104f2e1..1c602e1584 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From c8899aff92f793ee03bf61ab026263d2e6bd4dd6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Feb 2021 14:36:41 +0900 Subject: [PATCH 100/101] Prevent the default on-click sample from playing for OsuCheckbox --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index f6effa0834..0d00bc0dce 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.X, }, Nub = new Nub(), - new HoverClickSounds() + new HoverSounds() }; if (nubOnRight) From eaa7b4cb93cdad7a27bc8958cacd860a7f2e3b10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:54:50 +0900 Subject: [PATCH 101/101] Rename second usage variable name to match --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 4a03c5e614..ab4f3f4796 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -82,15 +82,15 @@ namespace osu.Game.Screens.Select.Details mods.BindValueChanged(modsChanged, true); } - private ModSettingChangeTracker settingChangeTracker; + private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedStatisticsUpdate; private void modsChanged(ValueChangedEvent> mods) { - settingChangeTracker?.Dispose(); + modSettingChangeTracker?.Dispose(); - settingChangeTracker = new ModSettingChangeTracker(mods.NewValue); - settingChangeTracker.SettingChanged += m => + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += m => { if (!(m is IApplicableToDifficulty)) return; @@ -162,7 +162,7 @@ namespace osu.Game.Screens.Select.Details protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - settingChangeTracker?.Dispose(); + modSettingChangeTracker?.Dispose(); starDifficultyCancellationSource?.Cancel(); }